diff --git a/.editorconfig b/.editorconfig index 7051372383..bfeab8c84f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -138,30 +138,25 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. -# CS0649: Field 'field' is never assigned to, and will always have its default value 'value' -dotnet_diagnostic.CS0649.severity = error - -# CS0162: Remove unreachable code -dotnet_diagnostic.CS0162.severity = error # CA1018: Mark attributes with AttributeUsageAttribute -dotnet_diagnostic.CA1018.severity = error +dotnet_diagnostic.CA1018.severity = warning # CA1304: Specify CultureInfo dotnet_diagnostic.CA1304.severity = warning # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = warning # CA1813: Avoid unsealed attributes -dotnet_diagnostic.CA1813.severity = error +dotnet_diagnostic.CA1813.severity = warning # CA1815: Override equals and operator equals on value types dotnet_diagnostic.CA1815.severity = warning # CA1820: Test for empty strings using string length dotnet_diagnostic.CA1820.severity = warning # CA1821: Remove empty finalizers -dotnet_diagnostic.CA1821.severity = error +dotnet_diagnostic.CA1821.severity = warning # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = suggestion dotnet_code_quality.CA1822.api_surface = private, internal # CA1823: Avoid unused private fields -dotnet_diagnostic.CA1823.severity = error +dotnet_diagnostic.CA1823.severity = warning # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning # CA1826: Use property instead of Linq Enumerable method @@ -179,48 +174,48 @@ dotnet_diagnostic.CA1851.severity = warning #CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method dotnet_diagnostic.CA1854.severity = warning #CA2211:Non-constant fields should not be visible -dotnet_diagnostic.CA2211.severity = error +dotnet_diagnostic.CA2211.severity = warning # Wrapping preferences csharp_wrap_before_ternary_opsigns = false # Avalonia DevAnalyzer preferences -dotnet_diagnostic.AVADEV2001.severity = error +dotnet_diagnostic.AVADEV2001.severity = warning # Avalonia PublicAnalyzer preferences -dotnet_diagnostic.AVP1000.severity = error -dotnet_diagnostic.AVP1001.severity = error -dotnet_diagnostic.AVP1002.severity = error -dotnet_diagnostic.AVP1010.severity = error -dotnet_diagnostic.AVP1011.severity = error +dotnet_diagnostic.AVP1000.severity = warning +dotnet_diagnostic.AVP1001.severity = warning +dotnet_diagnostic.AVP1002.severity = warning +dotnet_diagnostic.AVP1010.severity = warning +dotnet_diagnostic.AVP1011.severity = warning dotnet_diagnostic.AVP1012.severity = warning -dotnet_diagnostic.AVP1013.severity = error -dotnet_diagnostic.AVP1020.severity = error -dotnet_diagnostic.AVP1021.severity = error -dotnet_diagnostic.AVP1022.severity = error -dotnet_diagnostic.AVP1030.severity = error -dotnet_diagnostic.AVP1031.severity = error -dotnet_diagnostic.AVP1032.severity = error -dotnet_diagnostic.AVP1040.severity = error -dotnet_diagnostic.AVA2001.severity = error +dotnet_diagnostic.AVP1013.severity = warning +dotnet_diagnostic.AVP1020.severity = warning +dotnet_diagnostic.AVP1021.severity = warning +dotnet_diagnostic.AVP1022.severity = warning +dotnet_diagnostic.AVP1030.severity = warning +dotnet_diagnostic.AVP1031.severity = warning +dotnet_diagnostic.AVP1032.severity = warning +dotnet_diagnostic.AVP1040.severity = warning +dotnet_diagnostic.AVA2001.severity = warning # Xaml files [*.{xaml,axaml}] indent_size = 2 # DuplicateSetterError -avalonia_xaml_diagnostic.AVLN2203.severity = error +avalonia_xaml_diagnostic.AVLN2203.severity = warning # StyleInMergedDictionaries -avalonia_xaml_diagnostic.AVLN2204.severity = error +avalonia_xaml_diagnostic.AVLN2204.severity = warning # RequiredTemplatePartMissing -avalonia_xaml_diagnostic.AVLN2205.severity = error +avalonia_xaml_diagnostic.AVLN2205.severity = warning # OptionalTemplatePartMissing avalonia_xaml_diagnostic.AVLN2206.severity = info # TemplatePartWrongType -avalonia_xaml_diagnostic.AVLN2207.severity = error +avalonia_xaml_diagnostic.AVLN2207.severity = warning # ItemContainerInsideTemplate -avalonia_xaml_diagnostic.AVLN2208.severity = error +avalonia_xaml_diagnostic.AVLN2208.severity = warning # Obsolete -avalonia_xaml_diagnostic.AVLN5001.severity = error +avalonia_xaml_diagnostic.AVLN5001.severity = warning # Xml project files [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] diff --git a/Avalonia.sln b/Avalonia.sln index 1083462dcc..06c3e051ac 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -106,7 +106,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\TargetFrameworks.props = build\TargetFrameworks.props build\TrimmingEnable.props = build\TrimmingEnable.props build\UnitTests.NetFX.props = build\UnitTests.NetFX.props - build\WarnAsErrors.props = build\WarnAsErrors.props build\XUnit.props = build\XUnit.props EndProjectSection EndProject diff --git a/Directory.Build.props b/Directory.Build.props index 4498f87dab..400198daf9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,17 +1,18 @@ - - $(MSBuildThisFileDirectory)build-intermediate/nuget - $(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\$(AvsCurrentTargetFramework)\Avalonia.Designer.HostApp.dll - - false - False - 14.0 - true - true - true - true + $(MSBuildThisFileDirectory)build-intermediate/nuget + $(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\$(AvsCurrentTargetFramework)\Avalonia.Designer.HostApp.dll + + false + False + 14.0 + $(AvnTreatWarningsAsErrors) + true + true + true + true + true diff --git a/api/Avalonia.Headless.XUnit.nupkg.xml b/api/Avalonia.Headless.XUnit.nupkg.xml new file mode 100644 index 0000000000..c87cf909fe --- /dev/null +++ b/api/Avalonia.Headless.XUnit.nupkg.xml @@ -0,0 +1,76 @@ + + + + + CP0001 + T:Avalonia.Headless.XUnit.AvaloniaTestFrameworkTypeDiscoverer + baseline/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + + + CP0001 + T:Avalonia.Headless.XUnit.AvaloniaUIFactDiscoverer + baseline/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + + + CP0001 + T:Avalonia.Headless.XUnit.AvaloniaTestFrameworkTypeDiscoverer + baseline/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + + + CP0001 + T:Avalonia.Headless.XUnit.AvaloniaUIFactDiscoverer + baseline/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + + + CP0002 + M:Avalonia.Headless.XUnit.AvaloniaFactAttribute.#ctor + baseline/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + + + CP0002 + M:Avalonia.Headless.XUnit.AvaloniaTheoryDiscoverer.#ctor(Xunit.Abstractions.IMessageSink) + baseline/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + + + CP0002 + M:Avalonia.Headless.XUnit.AvaloniaFactAttribute.#ctor + baseline/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + + + CP0002 + M:Avalonia.Headless.XUnit.AvaloniaTheoryDiscoverer.#ctor(Xunit.Abstractions.IMessageSink) + baseline/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + + + CP0007 + T:Avalonia.Headless.XUnit.AvaloniaTheoryDiscoverer + baseline/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + + + CP0007 + T:Avalonia.Headless.XUnit.AvaloniaTheoryDiscoverer + baseline/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + + + CP0008 + T:Avalonia.Headless.XUnit.AvaloniaTestFrameworkAttribute + baseline/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net10.0/Avalonia.Headless.XUnit.dll + + + CP0008 + T:Avalonia.Headless.XUnit.AvaloniaTestFrameworkAttribute + baseline/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + current/Avalonia.Headless.XUnit/lib/net8.0/Avalonia.Headless.XUnit.dll + + diff --git a/api/Avalonia.Win32.Interoperability.nupkg.xml b/api/Avalonia.Win32.Interoperability.nupkg.xml index 33fc2ac062..3672bb9b99 100644 --- a/api/Avalonia.Win32.Interoperability.nupkg.xml +++ b/api/Avalonia.Win32.Interoperability.nupkg.xml @@ -1,4 +1,4 @@ - + @@ -37,4 +37,4 @@ baseline/Avalonia.Win32.Interoperability/lib/net8.0-windows7.0/Avalonia.Win32.Interoperability.dll current/Avalonia.Win32.Interoperability/lib/net8.0-windows7.0/Avalonia.Win32.Interoperability.dll - \ No newline at end of file + diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index cab4bf5580..f8b5337574 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1,995 +1,839 @@ - + CP0001 - T:Avalonia.Media.IGlyphTypeface + T:Avalonia.Controls.Primitives.IScrollable baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0001 - T:Avalonia.Media.IGlyphTypeface - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll - - - CP0002 - M:Avalonia.Media.FontManager.TryGetGlyphTypeface(Avalonia.Media.Typeface,Avalonia.Media.IGlyphTypeface@) + T:Avalonia.Media.Fonts.FontFamilyLoader baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.FontMetrics.get_DesignEmHeight + CP0001 + T:Avalonia.Platform.IGeometryContext2 baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryAddGlyphTypeface(Avalonia.Media.IGlyphTypeface) + CP0001 + T:Avalonia.Utilities.StringTokenizer baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryAddGlyphTypeface(System.IO.Stream,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Controls.Primitives.IScrollable + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryAddGlyphTypeface(System.String,Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Media.Fonts.FontFamilyLoader + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryCreateSyntheticGlyphTypeface(Avalonia.Media.IGlyphTypeface,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Controls.Primitives.IScrollable + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetGlyphTypeface(System.String,Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Media.Fonts.FontFamilyLoader + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Platform.IGeometryContext2 + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetNearestMatch(System.Collections.Generic.IDictionary{Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface},Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Utilities.StringTokenizer + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetNearestMatch(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Controls.Primitives.IScrollable + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll - CP0002 - M:Avalonia.Media.Fonts.IFontCollection.TryGetGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 + T:Avalonia.Media.Fonts.FontFamilyLoader + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Height + F:Avalonia.Media.DrawingImage.ViewboxProperty baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Width + F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphRun.#ctor(Avalonia.Media.IGlyphTypeface,System.Double,System.ReadOnlyMemory{System.Char},System.Collections.Generic.IReadOnlyList{Avalonia.Media.TextFormatting.GlyphInfo},System.Nullable{Avalonia.Point},System.Int32) + M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphRun.#ctor(Avalonia.Media.IGlyphTypeface,System.Double,System.ReadOnlyMemory{System.Char},System.Collections.Generic.IReadOnlyList{System.UInt16},System.Nullable{Avalonia.Point},System.Int32) + M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphRun.get_GlyphTypeface + M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyph(System.UInt32) + M:Avalonia.Media.DrawingImage.get_Viewbox baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) + M:Avalonia.Media.DrawingImage.set_Viewbox(System.Nullable{Avalonia.Rect}) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvances(System.ReadOnlySpan{System.UInt16}) + M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphs(System.ReadOnlySpan{System.UInt32}) + M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetGlyph(System.UInt32,System.UInt16@) + M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetTable(System.UInt32,System.Byte[]@) + M:Avalonia.Media.StreamGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.ShapedBuffer.#ctor(System.ReadOnlyMemory{System.Char},System.Int32,Avalonia.Media.IGlyphTypeface,System.Double,System.SByte) + M:Avalonia.Media.StreamGeometryContext.LineTo(Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.ShapedBuffer.get_GlyphTypeface + M:Avalonia.Media.StreamGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextMetrics.#ctor(Avalonia.Media.IGlyphTypeface,System.Double) + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.IGlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.IGlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + M:Avalonia.Platform.IGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextShaperOptions.get_Typeface + M:Avalonia.Platform.IGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Typeface.get_GlyphTypeface + M:Avalonia.Platform.IGeometryContext.LineTo(Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Platform.IGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) + M:Avalonia.Visuals.Platform.PathGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IGlyphRunImpl.get_GlyphTypeface + M:Avalonia.Visuals.Platform.PathGeometryContext.LineTo(Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.IGlyphTypeface,System.Double,System.Collections.Generic.IReadOnlyList{Avalonia.Media.TextFormatting.GlyphInfo},Avalonia.Point) + M:Avalonia.Visuals.Platform.PathGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0002 - F:Avalonia.Controls.Documents.TextElement.LetterSpacingProperty + F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.Presenters.ContentPresenter.LetterSpacingProperty + F:Avalonia.Controls.TextBlock.LetterSpacingProperty baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.Primitives.TemplatedControl.LetterSpacingProperty + F:Avalonia.Controls.TextBox.LetterSpacingProperty baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.TextBlock.LetterSpacingProperty + M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.get_LetterSpacing + M:Avalonia.Controls.Design.GetDataContext(Avalonia.Controls.Templates.IDataTemplate) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.GetLetterSpacing(Avalonia.Controls.Control) + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Controls.Templates.IDataTemplate) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.set_LetterSpacing(System.Double) + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Styling.IStyle) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.SetLetterSpacing(Avalonia.Controls.Control,System.Double) + M:Avalonia.Controls.Design.SetDataContext(Avalonia.Controls.Templates.IDataTemplate,System.Object) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Presenters.ContentPresenter.get_LetterSpacing + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Presenters.ContentPresenter.set_LetterSpacing(System.Double) + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Primitives.TemplatedControl.get_LetterSpacing + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.ResourceDictionary,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Primitives.TemplatedControl.set_LetterSpacing(System.Double) + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.Control) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Height - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Width - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.Control) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.get_GlyphCount - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyph(System.UInt32) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvances(System.ReadOnlySpan{System.UInt16}) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphs(System.ReadOnlySpan{System.UInt32}) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_Bounds(Avalonia.PixelRect) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetGlyph(System.UInt32,System.UInt16@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_CurrentOrientation(Avalonia.Platform.ScreenOrientation) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetTable(System.UInt32,System.Byte[]@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_DisplayName(System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_IsPrimary(System.Boolean) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_Scaling(System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_WorkingArea(Avalonia.PixelRect) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IGlyphRunImpl.get_GlyphTypeface + F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache baseline/Avalonia/lib/net6.0/Avalonia.Base.dll current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.FontManager.TryGetGlyphTypeface(Avalonia.Media.Typeface,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll - - - CP0002 - M:Avalonia.Media.FontMetrics.get_DesignEmHeight - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll - - - CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryAddGlyphTypeface(Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryAddGlyphTypeface(System.IO.Stream,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryAddGlyphTypeface(System.String,Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryCreateSyntheticGlyphTypeface(Avalonia.Media.IGlyphTypeface,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType) + baseline/Avalonia/lib/net6.0/Avalonia.Dialogs.dll + current/Avalonia/lib/net6.0/Avalonia.Dialogs.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetGlyphTypeface(System.String,Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface@) + F:Avalonia.Media.DrawingImage.ViewboxProperty baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetNearestMatch(System.Collections.Generic.IDictionary{Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface},Avalonia.Media.Fonts.FontCollectionKey,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.FontCollectionBase.TryGetNearestMatch(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Fonts.IFontCollection.TryGetGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Height + M:Avalonia.Media.DrawingImage.get_Viewbox baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Width + M:Avalonia.Media.DrawingImage.set_Viewbox(System.Nullable{Avalonia.Rect}) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphRun.#ctor(Avalonia.Media.IGlyphTypeface,System.Double,System.ReadOnlyMemory{System.Char},System.Collections.Generic.IReadOnlyList{Avalonia.Media.TextFormatting.GlyphInfo},System.Nullable{Avalonia.Point},System.Int32) + M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphRun.#ctor(Avalonia.Media.IGlyphTypeface,System.Double,System.ReadOnlyMemory{System.Char},System.Collections.Generic.IReadOnlyList{System.UInt16},System.Nullable{Avalonia.Point},System.Int32) + M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.GlyphRun.get_GlyphTypeface + M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.get_GlyphCount + M:Avalonia.Media.StreamGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyph(System.UInt32) + M:Avalonia.Media.StreamGeometryContext.LineTo(Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) + M:Avalonia.Media.StreamGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvances(System.ReadOnlySpan{System.UInt16}) + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphs(System.ReadOnlySpan{System.UInt32}) + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetGlyph(System.UInt32,System.UInt16@) + M:Avalonia.Platform.IGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetTable(System.UInt32,System.Byte[]@) + M:Avalonia.Platform.IGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.ShapedBuffer.#ctor(System.ReadOnlyMemory{System.Char},System.Int32,Avalonia.Media.IGlyphTypeface,System.Double,System.SByte) + M:Avalonia.Platform.IGeometryContext.LineTo(Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.ShapedBuffer.get_GlyphTypeface + M:Avalonia.Platform.IGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextMetrics.#ctor(Avalonia.Media.IGlyphTypeface,System.Double) + M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.IGlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + M:Avalonia.Visuals.Platform.PathGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.IGlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + M:Avalonia.Visuals.Platform.PathGeometryContext.LineTo(Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.TextFormatting.TextShaperOptions.get_Typeface + M:Avalonia.Visuals.Platform.PathGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.Typeface.get_GlyphTypeface - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + F:Avalonia.Controls.TextBlock.LetterSpacingProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + F:Avalonia.Controls.TextBox.LetterSpacingProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.GetDataContext(Avalonia.Controls.Templates.IDataTemplate) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IGlyphRunImpl.get_GlyphTypeface - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Controls.Templates.IDataTemplate) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.IGlyphTypeface,System.Double,System.Collections.Generic.IReadOnlyList{Avalonia.Media.TextFormatting.GlyphInfo},Avalonia.Point) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Styling.IStyle) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.Documents.TextElement.LetterSpacingProperty + M:Avalonia.Controls.Design.SetDataContext(Avalonia.Controls.Templates.IDataTemplate,System.Object) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.Presenters.ContentPresenter.LetterSpacingProperty + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.Primitives.TemplatedControl.LetterSpacingProperty + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - F:Avalonia.Controls.TextBlock.LetterSpacingProperty + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.ResourceDictionary,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.get_LetterSpacing + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.Control) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.GetLetterSpacing(Avalonia.Controls.Control) + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.set_LetterSpacing(System.Double) + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.Control) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Documents.TextElement.SetLetterSpacing(Avalonia.Controls.Control,System.Double) + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Presenters.ContentPresenter.get_LetterSpacing + M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Presenters.ContentPresenter.set_LetterSpacing(System.Double) + M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Primitives.TemplatedControl.get_LetterSpacing + M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Controls.Primitives.TemplatedControl.set_LetterSpacing(System.Double) + M:Avalonia.Platform.Screen.set_Bounds(Avalonia.PixelRect) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Height - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_CurrentOrientation(Avalonia.Platform.ScreenOrientation) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.GlyphMetrics.get_Width - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_DisplayName(System.String) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.get_GlyphCount - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_IsPrimary(System.Boolean) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyph(System.UInt32) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_Scaling(System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.Screen.set_WorkingArea(Avalonia.PixelRect) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvances(System.ReadOnlySpan{System.UInt16}) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType) + baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll + current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.GetGlyphs(System.ReadOnlySpan{System.UInt32}) + F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetGlyph(System.UInt32,System.UInt16@) + M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl) baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Media.IGlyphTypeface.TryGetTable(System.UInt32,System.Byte[]@) + M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl) baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Dialogs.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Dialogs.dll CP0002 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.IDrawingContextImplWithEffects.PopEffect + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Platform.IGlyphRunImpl.get_GlyphTypeface - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryCreateSyntheticGlyphTypeface(Avalonia.Media.GlyphTypeface,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.GlyphTypeface@) + M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType}) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryCreateSyntheticGlyphTypeface(Avalonia.Media.IGlyphTypeface,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Platform.IDrawingContextImpl.PopTextOptions baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetFamilyTypefaces(System.String,System.Collections.Generic.IReadOnlyList{Avalonia.Media.Typeface}@) + M:Avalonia.Platform.IDrawingContextImpl.PushTextOptions(Avalonia.Media.TextOptions) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.GlyphTypeface@) + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(System.Nullable{Avalonia.Rect},Avalonia.Media.IEffect) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetNearestMatch(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.GlyphTypeface@) + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetNearestMatch(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Platform.IGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection,System.Boolean) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) + M:Avalonia.Platform.IGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point,System.Boolean) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IPlatformTypeface@) + M:Avalonia.Platform.IGeometryContext.LineTo(Avalonia.Point,System.Boolean) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IPlatformTypeface@) + M:Avalonia.Platform.IGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point,System.Boolean) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryGetFamilyTypefaces(System.String,System.Collections.Generic.IReadOnlyList{Avalonia.Media.Typeface}@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IClipboard.TryGetDataAsync + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphTypeface,System.Double,System.Collections.Generic.IReadOnlyList{Avalonia.Media.TextFormatting.GlyphInfo},Avalonia.Point) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataAsync + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.ITextShaperImpl.CreateTypeface(Avalonia.Media.GlyphTypeface) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.ITextShaperImpl.CreateTypeface(Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.CharacterToGlyphMap - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.FaceNames - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.Platform.Storage.IStorageProvider.SaveFilePickerWithResultAsync(Avalonia.Platform.Storage.FilePickerSaveOptions) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.FamilyNames - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.PlatformTypeface - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.SupportedFeatures - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll + P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType + baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.TextShaperTypeface - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.TypographicFamilyName - baseline/Avalonia/lib/net10.0/Avalonia.Base.dll - current/Avalonia/lib/net10.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Input.Platform.IClipboard.TryGetDataAsync - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataAsync - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryGetFamilyTypefaces(System.String,System.Collections.Generic.IReadOnlyList{Avalonia.Media.Typeface}@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.Platform.ITextShaperImpl.CreateTypeface(Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.CharacterToGlyphMap - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.FaceNames - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.FamilyNames - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.GlyphCount - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.PlatformTypeface - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.SupportedFeatures - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.TextShaperTypeface - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - P:Avalonia.Media.IGlyphTypeface.TypographicFamilyName - baseline/Avalonia/lib/net6.0/Avalonia.Base.dll - current/Avalonia/lib/net6.0/Avalonia.Base.dll - - - CP0006 - M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) - baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll - current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll - - - CP0006 - M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) - baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll - current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll - - - CP0006 - P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType - baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll - current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll + M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType}) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 @@ -1017,296 +861,296 @@ CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryCreateSyntheticGlyphTypeface(Avalonia.Media.GlyphTypeface,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.GlyphTypeface@) + M:Avalonia.Platform.IDrawingContextImpl.PopTextOptions baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryCreateSyntheticGlyphTypeface(Avalonia.Media.IGlyphTypeface,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Platform.IDrawingContextImpl.PushTextOptions(Avalonia.Media.TextOptions) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetFamilyTypefaces(System.String,System.Collections.Generic.IReadOnlyList{Avalonia.Media.Typeface}@) + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(System.Nullable{Avalonia.Rect},Avalonia.Media.IEffect) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.GlyphTypeface@) + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetNearestMatch(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.GlyphTypeface@) + M:Avalonia.Platform.IGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection,System.Boolean) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.Fonts.IFontCollection.TryGetNearestMatch(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IGlyphTypeface@) + M:Avalonia.Platform.IGeometryContext.CubicBezierTo(Avalonia.Point,Avalonia.Point,Avalonia.Point,System.Boolean) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) + M:Avalonia.Platform.IGeometryContext.LineTo(Avalonia.Point,System.Boolean) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IPlatformTypeface@) + M:Avalonia.Platform.IGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point,System.Boolean) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IPlatformTypeface@) + M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryGetFamilyTypefaces(System.String,System.Collections.Generic.IReadOnlyList{Avalonia.Media.Typeface}@) + M:Avalonia.Platform.Storage.IStorageProvider.SaveFilePickerWithResultAsync(Avalonia.Platform.Storage.FilePickerSaveOptions) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll CP0006 - M:Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphTypeface,System.Double,System.Collections.Generic.IReadOnlyList{Avalonia.Media.TextFormatting.GlyphInfo},Avalonia.Point) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType + baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll CP0006 - M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.ITextShaperImpl.CreateTypeface(Avalonia.Media.GlyphTypeface) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IClipboard.TryGetDataAsync + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - M:Avalonia.Platform.ITextShaperImpl.CreateTypeface(Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataAsync + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.CharacterToGlyphMap - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.FaceNames - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.FamilyNames - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.GlyphCount - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.Platform.Storage.IStorageProvider.SaveFilePickerWithResultAsync(Avalonia.Platform.Storage.FilePickerSaveOptions) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.PlatformTypeface - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll + current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.SupportedFeatures - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll + current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll CP0006 - P:Avalonia.Media.IGlyphTypeface.TextShaperTypeface - baseline/Avalonia/lib/net8.0/Avalonia.Base.dll - current/Avalonia/lib/net8.0/Avalonia.Base.dll + P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType + baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll + current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.TypographicFamilyName + CP0008 + T:Avalonia.Media.StreamGeometryContext + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0008 + T:Avalonia.Media.StreamGeometryContext baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0006 - M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) - baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll - current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + CP0009 + T:Avalonia.Platform.Screen + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll - CP0006 - M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) - baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll - current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + CP0009 + T:Avalonia.Platform.Screen + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll - CP0006 - P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType - baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll - current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Count + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Input.Platform.IClipboard.TryGetDataAsync - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataAsync - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Count + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Media.IGlyphTypeface.GetGlyphAdvance(System.UInt16) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Platform.Screen.Equals(Avalonia.Platform.Screen) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.IO.Stream,Avalonia.Media.FontSimulations,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Count + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryCreateGlyphTypeface(System.String,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryGetFamilyTypefaces(System.String,System.Collections.Generic.IReadOnlyList{Avalonia.Media.Typeface}@) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.IPlatformTypeface@) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Count + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32) + baseline/Avalonia/lib/net6.0/Avalonia.Base.dll + current/Avalonia/lib/net6.0/Avalonia.Base.dll - CP0006 - M:Avalonia.Platform.ITextShaperImpl.CreateTypeface(Avalonia.Media.IGlyphTypeface) - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Count + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.CharacterToGlyphMap - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.FaceNames - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.FamilyNames - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Count + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.GlyphCount - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.PlatformTypeface - baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + CP0012 + M:Avalonia.Platform.Screen.Equals(Avalonia.Platform.Screen) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.SupportedFeatures + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Count baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.TextShaperTypeface + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32) baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - CP0006 - P:Avalonia.Media.IGlyphTypeface.TypographicFamilyName + CP0012 + M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - CP0006 - M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) - baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll - current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll - - - CP0006 - M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) - baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll - current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Count + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - CP0006 - P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType - baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll - current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll + CP0012 + P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32) + baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - \ No newline at end of file + diff --git a/build/AnalyzerProject.targets b/build/AnalyzerProject.targets index c1b96ebe04..4a95cb306e 100644 --- a/build/AnalyzerProject.targets +++ b/build/AnalyzerProject.targets @@ -3,6 +3,7 @@ true true + true $(NoWarn);RS2008 diff --git a/build/NullableEnable.props b/build/NullableEnable.props index ef62ecc50c..60b91b85a3 100644 --- a/build/NullableEnable.props +++ b/build/NullableEnable.props @@ -1,11 +1,7 @@ - enable - $(WarningsAsErrors);nullable - $(NoWarn);nullable + + $(NoWarn);nullable diff --git a/build/TrimmingEnable.props b/build/TrimmingEnable.props index 34b3e232b6..cf84ae67b4 100644 --- a/build/TrimmingEnable.props +++ b/build/TrimmingEnable.props @@ -1,28 +1,16 @@ - + false true false true - - - true + $(TreatWarningsAsErrors) - - true - - $(WarningsAsErrors);IL2000;IL2001;IL2002;IL2003;IL2004;IL2005;IL2006;IL2007;IL2008;IL2009;IL2010;IL2011;IL2012;IL2013;IL2014;IL2015;IL2016;IL2017;IL2018;IL2019;IL2020;IL2021;IL2022;IL2023;IL2024;IL2025;IL2026;IL2027;IL2028;IL2029;IL2030;IL2031;IL2032;IL2033;IL2034;IL2035;IL2036;IL2037;IL2038;IL2039;IL2040;IL2041;IL2042;IL2043;IL2044;IL2045;IL2046;IL2047;IL2048;IL2049;IL2050;IL2051;IL2052;IL2053;IL2054;IL2055;IL2056;IL2057;IL2058;IL2059;IL2060;IL2061;IL2062;IL2063;IL2064;IL2065;IL2066;IL2067;IL2068;IL2069;IL2070;IL2071;IL2072;IL2073;IL2074;IL2075;IL2076;IL2077;IL2078;IL2079;IL2080;IL2081;IL2082;IL2083;IL2084;IL2085;IL2086;IL2087;IL2088;IL2089;IL2090;IL2091;IL2092;IL2093;IL2094;IL2095;IL2096;IL2097;IL2098;IL2099;IL2100;IL2101;IL2102;IL2103;IL2104;IL2105;IL2106;IL2107;IL2108;IL2109;IL2110;IL2111;IL2112;IL2113;IL2114;IL2115;IL2116;IL2117;IL2118;IL2119;IL2120;IL2121;IL2122;IL2123;IL2124;IL2125;IL2126;IL2127;IL2128;IL2129;IL2130;IL2131;IL2132;IL2133;IL2134;IL2135;IL2136;IL2137;IL2138;IL2139;IL2140;IL2141;IL2142;IL2143;IL2144;IL2145;IL2146;IL2147;IL2148;IL2149;IL2150;IL2151;IL2152;IL2153;IL2154;IL2155;IL2156;IL2157 - - $(WarningsAsErrors);IL3050;IL3051;IL3052;IL3053;IL3054;IL3055;IL3056 - - - - $(WarningsAsErrors);CA1420;CA1421 - + diff --git a/build/WarnAsErrors.props b/build/WarnAsErrors.props deleted file mode 100644 index eaab7bd00c..0000000000 --- a/build/WarnAsErrors.props +++ /dev/null @@ -1,23 +0,0 @@ - - - - - $(WarningsNotAsErrors);CS0649 - - $(WarningsNotAsErrors);CS0162 - - $(WarningsNotAsErrors);CA2211 - - $(WarningsNotAsErrors);CA1821 - - $(WarningsNotAsErrors);CA1823 - - $(WarningsNotAsErrors);AVLN2203 - - $(WarningsNotAsErrors);AVLN2205 - - $(WarningsNotAsErrors);AVLN2207 - - $(WarningsNotAsErrors);AVLN2208 - - diff --git a/build/XUnit.props b/build/XUnit.props index c2ccb1fa2c..5c63ed69db 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -1,14 +1,13 @@  - + + - - - - + $(MSBuildThisFileDirectory)\avalonia.snk False $(NoWarn);CS8002 + diff --git a/docs/build.md b/docs/build.md index cef5ed3e10..2285e56d01 100644 --- a/docs/build.md +++ b/docs/build.md @@ -71,7 +71,7 @@ And run tests: Or if you need to create nuget packages as well (it will compile and run tests automatically): `nuke --target Package --configuration Release` -Alternatively, you can run nuke build direclty without installing Nuke global tool: +Alternatively, you can run nuke build directly without installing Nuke global tool: `dotnet run --project nukebuild/_build.csproj -- --configuration Debug` # Linux/macOS diff --git a/external/Numerge b/external/Numerge index 9738c6121f..5530e1cbe9 160000 --- a/external/Numerge +++ b/external/Numerge @@ -1 +1 @@ -Subproject commit 9738c6121fdd143c78d3e25686a7e4e13c00f586 +Subproject commit 5530e1cbe9e105ff4ebc9da1f4af3253a8756754 diff --git a/native/Avalonia.Native/src/OSX/AvnAccessibility.h b/native/Avalonia.Native/src/OSX/AvnAccessibility.h index 6658d8523e..4f8b50ecc5 100644 --- a/native/Avalonia.Native/src/OSX/AvnAccessibility.h +++ b/native/Avalonia.Native/src/OSX/AvnAccessibility.h @@ -7,7 +7,6 @@ @protocol AvnAccessibility @required - (void) raiseChildrenChanged; -@optional - (void) raiseFocusChanged; - (void) raisePropertyChanged:(AvnAutomationProperty)property; @end diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 6d49fc85e0..1e7c98cb3a 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -656,5 +656,9 @@ NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); } +- (void)raisePropertyChanged:(AvnAutomationProperty)property +{ +} + @end diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 30e1f5ea00..5cd4edaa54 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -11,6 +11,7 @@ IAvnAutomationPeer* _peer; AvnAutomationNode* _node; NSMutableArray* _children; + NSArray* _attributeNames; } + (NSAccessibilityElement *)acquire:(IAvnAutomationPeer *)peer @@ -126,6 +127,7 @@ case AutomationHeaderItem: return NSAccessibilityButtonRole; case AutomationTable: return NSAccessibilityTableRole; case AutomationTitleBar: return NSAccessibilityGroupRole; + case AutomationExpander: return NSAccessibilityDisclosureTriangleRole; // Treat unknown roles as generic group container items. Returning // NSAccessibilityUnknownRole is also possible but makes the screen // reader focus on the item instead of passing focus to child items. @@ -165,6 +167,32 @@ return NSAccessibilityRoleDescription([self accessibilityRole], [self accessibilitySubrole]); } +// Note: Apple has deprecated this API, but it's still used to set attributes not supported by NSAccessibility +- (NSArray *)accessibilityAttributeNames +{ + if (_attributeNames == nil) + { + _attributeNames = @[ + @"AXARIALive", // kAXARIALiveAttribute + ]; + } + return _attributeNames; +} + +- (id)accessibilityAttributeValue:(NSAccessibilityAttributeName)attribute +{ + if ([attribute isEqualToString:@"AXARIALive" /* kAXARIALiveAttribute */]) + { + switch (_peer->GetLiveSetting()) + { + case LiveSettingPolite: return @"polite"; + case LiveSettingAssertive: return @"assertive"; + } + return nil; + } + return nil; +} + - (NSString *)accessibilityIdentifier { return GetNSStringAndRelease(_peer->GetAutomationId()); @@ -420,8 +448,15 @@ @{ NSAccessibilityUIElementsKey: [changed allObjects]}); } -- (void)raisePropertyChanged +- (void)raisePropertyChanged:(AvnAutomationProperty)property +{ + if (property == AutomationPeer_Name && _peer->GetLiveSetting() != LiveSettingOff) + [self raiseLiveRegionChanged]; +} + +- (void)raiseLiveRegionChanged { + NSAccessibilityPostNotification(self, @"AXLiveRegionChanged" /* kAXLiveRegionChangedNotification */); } - (void)setAccessibilityFocused:(BOOL)accessibilityFocused diff --git a/nukebuild/.editorconfig b/nukebuild/.editorconfig index c11849c8e9..d6009b3c0f 100644 --- a/nukebuild/.editorconfig +++ b/nukebuild/.editorconfig @@ -6,12 +6,3 @@ root = false # C# files [*.cs] dotnet_style_require_accessibility_modifiers = never - -[{il-repack,Numerge}/**/*.cs] -dotnet_diagnostic.CA1304.severity = none -dotnet_diagnostic.CA1815.severity = none -dotnet_diagnostic.CA1820.severity = none -dotnet_diagnostic.CA1825.severity = none -dotnet_diagnostic.CA1829.severity = none -dotnet_diagnostic.CA1847.severity = none -dotnet_diagnostic.SYSLIB0017.severity = none diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 075715346d..c3ee103e73 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -4,11 +4,9 @@ false False - $(NoWarn);CS0649;CS0169;SYSLIB0011 + $(NoWarn);CS0649;CA1847 1 $(AvsCurrentTargetFramework) - - true diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props index 78656a1726..c36f495ea5 100644 --- a/packages/Avalonia/Avalonia.props +++ b/packages/Avalonia/Avalonia.props @@ -1,6 +1,6 @@ - $(MSBuildThisFileDirectory)\..\tools\$(AvsCurrentTargetFramework)\designer\Avalonia.Designer.HostApp.dll + $(MSBuildThisFileDirectory)\..\tools\net8.0\designer\Avalonia.Designer.HostApp.dll $(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Avalonia.Build.Tasks.dll $(UsedAvaloniaProducts);AvaloniaUI true diff --git a/samples/IntegrationTestApp/Pages/AutomationPage.axaml b/samples/IntegrationTestApp/Pages/AutomationPage.axaml index dcc1ee479c..c02bc1baa6 100644 --- a/samples/IntegrationTestApp/Pages/AutomationPage.axaml +++ b/samples/IntegrationTestApp/Pages/AutomationPage.axaml @@ -22,5 +22,8 @@ Header None + + + This is an assertive live region. diff --git a/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs b/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs index f79000126c..7bf37c175f 100644 --- a/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs +++ b/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Interactivity; namespace IntegrationTestApp.Pages; @@ -8,4 +9,9 @@ public partial class AutomationPage : UserControl { InitializeComponent(); } + + private void OnButtonAddSomeText(object? sender, RoutedEventArgs? e) + { + textLiveRegion.Text += " Lorem ipsum."; + } } diff --git a/samples/TextTestApp/InteractiveLineControl.cs b/samples/TextTestApp/InteractiveLineControl.cs index 362be52ac3..7af977f9a1 100644 --- a/samples/TextTestApp/InteractiveLineControl.cs +++ b/samples/TextTestApp/InteractiveLineControl.cs @@ -336,7 +336,7 @@ namespace TextTestApp _textSource = new TextSource(this); RenderOptions.SetEdgeMode(this, EdgeMode.Aliased); - RenderOptions.SetTextRenderingMode(this, TextRenderingMode.SubpixelAntialias); + TextOptions.SetTextRenderingMode(this, TextRenderingMode.SubpixelAntialias); } private void InvalidateTextRunProperties() diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 665feb2e2b..86b96772ce 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -39,6 +39,7 @@ namespace Avalonia.Android OnConfigurationChanged(); _view.InternalView.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; + _view.InternalView.SurfaceWindowDestroyed += InternalView_SurfaceWindowDestroyed; _accessHelper = new AvaloniaAccessHelper(this); ViewCompat.SetAccessibilityDelegate(this, _accessHelper); @@ -51,9 +52,18 @@ namespace Avalonia.Android if (Visibility == ViewStates.Visible) { OnVisibilityChanged(true); + + _root?.InvalidateMeasure(); + Invalidate(); } } + private void InternalView_SurfaceWindowDestroyed(object? sender, EventArgs e) + { + OnVisibilityChanged(false); + _surfaceCreated = false; + } + internal TopLevelImpl TopLevelImpl => _view; internal TopLevel? TopLevel => _root; diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs index 4a6058e078..3cdda52764 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs @@ -18,6 +18,8 @@ namespace Avalonia.Android private double _scaling = 1; public event EventHandler? SurfaceWindowCreated; + public event EventHandler? SurfaceWindowDestroyed; + public PixelSize Size => _size; public double Scaling => _scaling; @@ -61,6 +63,7 @@ namespace Avalonia.Android .Log(this, "InvalidationAwareSurfaceView Destroyed"); ReleaseNativeWindowHandle(); _size = new PixelSize(1, 1); + SurfaceWindowDestroyed?.Invoke(this, EventArgs.Empty); } public virtual void SurfaceRedrawNeeded(ISurfaceHolder holder) diff --git a/src/Avalonia.Base/Controls/IInternalScroller.cs b/src/Avalonia.Base/Controls/IInternalScroller.cs deleted file mode 100644 index 226626731b..0000000000 --- a/src/Avalonia.Base/Controls/IInternalScroller.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Avalonia.Controls.Primitives; - -// TODO12: Integrate with existing IScrollable interface, breaking change -internal interface IInternalScroller -{ - bool CanHorizontallyScroll { get; } - - bool CanVerticallyScroll { get; } -} diff --git a/src/Avalonia.Controls/IScrollable.cs b/src/Avalonia.Base/Controls/Primitives/IScrollable.cs similarity index 63% rename from src/Avalonia.Controls/IScrollable.cs rename to src/Avalonia.Base/Controls/Primitives/IScrollable.cs index 680088290c..ceaaf38c05 100644 --- a/src/Avalonia.Controls/IScrollable.cs +++ b/src/Avalonia.Base/Controls/Primitives/IScrollable.cs @@ -19,5 +19,15 @@ namespace Avalonia.Controls.Primitives /// Gets the size of the viewport, in logical units. /// Size Viewport { get; } + + /// + /// Gets a value indicating whether the content can be scrolled horizontally. + /// + bool CanHorizontallyScroll { get; } + + /// + /// Gets a value indicating whether the content can be scrolled horizontally. + /// + bool CanVerticallyScroll { get; } } } diff --git a/src/Avalonia.Base/Data/CompiledBinding.cs b/src/Avalonia.Base/Data/CompiledBinding.cs new file mode 100644 index 0000000000..764b04957e --- /dev/null +++ b/src/Avalonia.Base/Data/CompiledBinding.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; +using Avalonia.Data.Core.ExpressionNodes; +using Avalonia.Data.Core.Parsers; + +namespace Avalonia.Data; + +/// +/// A binding which does not use reflection to access members. +/// +public class CompiledBinding : BindingBase +{ + /// + /// Initializes a new instance of the class. + /// + public CompiledBinding() { } + + /// + /// Initializes a new instance of the class. + /// + /// The binding path. + public CompiledBinding(CompiledBindingPath path) => Path = path; + + /// + /// Gets or sets the amount of time, in milliseconds, to wait before updating the binding + /// source after the value on the target changes. + /// + /// + /// There is no delay when the source is updated via + /// or . Nor is there a delay when + /// is active and a new source object is provided. + /// + public int Delay { get; set; } + + /// + /// Gets or sets the to use. + /// + public IValueConverter? Converter { get; set; } + + /// + /// Gets or sets the culture in which to evaluate the converter. + /// + /// The default value is null. + /// + /// If this property is not set then will be used. + /// + [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] + public CultureInfo? ConverterCulture { get; set; } + + /// + /// Gets or sets a parameter to pass to . + /// + public object? ConverterParameter { get; set; } + + /// + /// Gets or sets the value to use when the binding is unable to produce a value. + /// + public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue; + + /// + /// Gets or sets the binding mode. + /// + public BindingMode Mode { get; set; } + + /// + /// Gets or sets the binding path. + /// + public CompiledBindingPath? Path { get; set; } + + /// + /// Gets or sets the binding priority. + /// + public BindingPriority Priority { get; set; } + + /// + /// Gets or sets the source for the binding. + /// + public object? Source { get; set; } = AvaloniaProperty.UnsetValue; + + /// + /// Gets or sets the string format. + /// + public string? StringFormat { get; set; } + + /// + /// Gets or sets the value to use when the binding result is null. + /// + public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue; + + /// + /// Gets or sets a value that determines the timing of binding source updates for + /// and bindings. + /// + public UpdateSourceTrigger UpdateSourceTrigger { get; set; } + + internal WeakReference? DefaultAnchor { get; set; } + internal WeakReference? NameScope { get; set; } + + internal override BindingExpressionBase CreateInstance( + AvaloniaObject target, + AvaloniaProperty? targetProperty, + object? anchor) + { + var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false; + var nodes = new List(); + var isRooted = false; + + Path?.BuildExpression(nodes, out isRooted); + + // If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self, + // #elementName etc.) then we need to add a data context source node. + if (Source == AvaloniaProperty.UnsetValue && !isRooted) + nodes.Insert(0, ExpressionNodeFactory.CreateDataContext(targetProperty)); + + // If the first node is an ISourceNode then allow it to select the source; otherwise + // use the binding source if specified, falling back to the target. + var source = nodes?.Count > 0 && nodes[0] is SourceNode sn + ? sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) + : Source != AvaloniaProperty.UnsetValue ? Source : target; + + var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty); + + return new BindingExpression( + source, + nodes, + FallbackValue, + delay: TimeSpan.FromMilliseconds(Delay), + converter: Converter, + converterCulture: ConverterCulture, + converterParameter: ConverterParameter, + enableDataValidation: enableDataValidation, + mode: mode, + priority: Priority, + stringFormat: StringFormat, + targetNullValue: TargetNullValue, + targetProperty: targetProperty, + targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), + updateSourceTrigger: trigger); + } + + private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata( + AvaloniaObject target, + AvaloniaProperty? targetProperty) + { + var mode = Mode; + var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ? + UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger; + + if (mode == BindingMode.Default) + { + if (targetProperty?.GetMetadata(target) is { } metadata) + mode = metadata.DefaultBindingMode; + else + mode = BindingMode.OneWay; + } + + return (mode, trigger); + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Avalonia.Base/Data/CompiledBindingPath.cs similarity index 92% rename from src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs rename to src/Avalonia.Base/Data/CompiledBindingPath.cs index 20e9c6e886..aea320702e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Avalonia.Base/Data/CompiledBindingPath.cs @@ -4,10 +4,10 @@ using System.Reflection; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Data.Core.ExpressionNodes; +using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; -using Avalonia.Markup.Parsers; -namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +namespace Avalonia.Data { public class CompiledBindingPath { @@ -96,21 +96,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings /// public override string ToString() - => string.Concat((IEnumerable) _elements); + => string.Concat((IEnumerable)_elements); } public class CompiledBindingPathBuilder { - private readonly int _apiVersion; private readonly List _elements = new(); public CompiledBindingPathBuilder() { } - // TODO12: Remove this constructor. apiVersion is only needed for compatibility with - // versions of Avalonia which used $self.Property() for building TemplatedParent bindings. - public CompiledBindingPathBuilder(int apiVersion) => _apiVersion = apiVersion; public CompiledBindingPathBuilder Not() { @@ -120,22 +116,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CompiledBindingPathBuilder Property(IPropertyInfo info, Func, IPropertyInfo, IPropertyAccessor> accessorFactory) { - // Older versions of Avalonia used $self.Property() for building TemplatedParent bindings. - // Try to detect this and upgrade to using a TemplatedParentPathElement so that logging works - // correctly. - if (_apiVersion == 0 && - info.Name == "TemplatedParent" && - _elements.Count >= 1 && - _elements[_elements.Count - 1] is SelfPathElement) - { - _elements.Add(new TemplatedParentPathElement()); - } - else - { - return Property(info, accessorFactory, acceptsNull: false); - } - - return this; + return Property(info, accessorFactory, acceptsNull: false); } public CompiledBindingPathBuilder Property( @@ -285,7 +266,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public MethodInfo Method { get; } public Type DelegateType { get; } - + public bool AcceptsNull { get; } } diff --git a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs index 6ab0031f31..e82bb5d216 100644 --- a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs @@ -24,9 +24,11 @@ namespace Avalonia.Input /// The current element. /// The direction to move. /// Any key modifiers active at the time of focus. - void Move( + /// The device type used to move the focus. + bool Move( IInputElement element, NavigationDirection direction, - KeyModifiers keyModifiers = KeyModifiers.None); + KeyModifiers keyModifiers = KeyModifiers.None, + KeyDeviceType? deviceType = null); } } diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index 3444a88aba..e5e7eb0699 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -98,22 +98,12 @@ namespace Avalonia.Input return result; } - /// - /// Moves the focus in the specified direction. - /// - /// The current element. - /// The direction to move. - /// Any key modifiers active at the time of focus. - public void Move( + /// + 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); } } diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs index a4eea7d88b..0f529142ca 100644 --- a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs +++ b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs @@ -78,7 +78,7 @@ public partial class XYFocus return false; } - var closestScroller = candidate.FindAncestorOfType(true); + var closestScroller = candidate.FindAncestorOfType(true); return ReferenceEquals(closestScroller, activeScroller); } @@ -93,7 +93,7 @@ public partial class XYFocus var parent = activeScroller.Parent; while (parent != null) { - if (parent is IInternalScroller and Visual visual + if (parent is IScrollable and Visual visual && visual.IsVisualAncestorOf(candidate)) { return true; diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs index 20267f4c0c..929d92a650 100644 --- a/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs +++ b/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs @@ -415,7 +415,7 @@ public partial class XYFocus while (parent != null) { var element = parent; - if (element is IInternalScroller scrollable) + if (element is IScrollable scrollable) { var isHorizontallyScrollable = scrollable.CanHorizontallyScroll; var isVerticallyScrollable = scrollable.CanVerticallyScroll; diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs index 7f9870315b..36e14cd3fd 100644 --- a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs +++ b/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() { diff --git a/src/Avalonia.Base/Media/BaselinePixelAlignment.cs b/src/Avalonia.Base/Media/BaselinePixelAlignment.cs new file mode 100644 index 0000000000..37bd48c480 --- /dev/null +++ b/src/Avalonia.Base/Media/BaselinePixelAlignment.cs @@ -0,0 +1,26 @@ +namespace Avalonia.Media +{ + /// + /// Specifies the baseline pixel alignment options for rendering text or graphics. + /// + /// Use this enumeration to control whether the baseline of rendered content is aligned to the + /// pixel grid, which can affect visual crispness and positioning. The value may influence rendering quality, + /// especially at small font sizes or when precise alignment is required. + public enum BaselinePixelAlignment : byte + { + /// + /// The baseline pixel alignment is unspecified. + /// + Unspecified, + + /// + /// The baseline is aligned to the pixel grid. + /// + Aligned, + + /// + /// The baseline is not aligned to the pixel grid. + /// + Unaligned + } +} diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index bd2c43878d..0f73e4af98 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -284,7 +284,8 @@ namespace Avalonia.Media Clip, GeometryClip, OpacityMask, - RenderOptions + RenderOptions, + TextOptions } public RestoreState(DrawingContext context, PushedStateType type) @@ -311,6 +312,8 @@ namespace Avalonia.Media _context.PopOpacityMaskCore(); else if (_type == PushedStateType.RenderOptions) _context.PopRenderOptionsCore(); + else if (_type == PushedStateType.TextOptions) + _context.PopTextOptionsCore(); } } @@ -417,6 +420,20 @@ namespace Avalonia.Media } protected abstract void PushRenderOptionsCore(RenderOptions renderOptions); + /// + /// Pushes text options for the drawing context. + /// + /// The text options. + /// A disposable to undo the text options. + public PushedState PushTextOptions(TextOptions textOptions) + { + PushTextOptionsCore(textOptions); + _states ??= StateStackPool.Get(); + _states.Push(new RestoreState(this, RestoreState.PushedStateType.TextOptions)); + return new PushedState(this); + } + protected abstract void PushTextOptionsCore(TextOptions textOptions); + [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix); [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] @@ -433,6 +450,7 @@ namespace Avalonia.Media protected abstract void PopOpacityMaskCore(); protected abstract void PopTransformCore(); protected abstract void PopRenderOptionsCore(); + protected abstract void PopTextOptionsCore(); private static bool PenIsVisible(IPen? pen) { diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 7299bff850..75921196c0 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -54,6 +54,7 @@ namespace Avalonia.Media } internal RenderOptions? RenderOptions { get; set; } + internal TextOptions? TextOptions { get; set; } /// /// Gets or sets the collection that contains the child geometries. @@ -78,6 +79,7 @@ namespace Avalonia.Media using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default) using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default) using (RenderOptions != null ? context.PushRenderOptions(RenderOptions.Value) : default) + using (TextOptions != null ? context.PushTextOptions(TextOptions.Value) : default) { foreach (var drawing in Children) { @@ -325,6 +327,15 @@ namespace Avalonia.Media drawingGroup.RenderOptions = renderOptions; } + protected override void PushTextOptionsCore(TextOptions textOptions) + { + // Instantiate a new drawing group and set it as the _currentDrawingGroup + var drawingGroup = PushNewDrawingGroup(); + + // Set the text options on the new DrawingGroup + drawingGroup.TextOptions = textOptions; + } + protected override void PopClipCore() => Pop(); protected override void PopGeometryClipCore() => Pop(); @@ -337,6 +348,8 @@ namespace Avalonia.Media protected override void PopRenderOptionsCore() => Pop(); + protected override void PopTextOptionsCore() => Pop(); + /// /// Creates a new DrawingGroup for a Push* call by setting the /// _currentDrawingGroup to a newly instantiated DrawingGroup, diff --git a/src/Avalonia.Base/Media/PlatformDrawingContext.cs b/src/Avalonia.Base/Media/PlatformDrawingContext.cs index 312cae2c52..12eef33a6d 100644 --- a/src/Avalonia.Base/Media/PlatformDrawingContext.cs +++ b/src/Avalonia.Base/Media/PlatformDrawingContext.cs @@ -85,6 +85,8 @@ internal sealed class PlatformDrawingContext : DrawingContext } protected override void PushRenderOptionsCore(RenderOptions renderOptions) => _impl.PushRenderOptions(renderOptions); + + protected override void PushTextOptionsCore(TextOptions textOptions) => _impl.PushTextOptions(textOptions); protected override void PopClipCore() => _impl.PopClip(); @@ -99,6 +101,8 @@ internal sealed class PlatformDrawingContext : DrawingContext (_transforms ?? throw new ObjectDisposedException(nameof(PlatformDrawingContext))).Pop(); protected override void PopRenderOptionsCore() => _impl.PopRenderOptions(); + + protected override void PopTextOptionsCore() => _impl.PopTextOptions(); protected override void DisposeCore() { diff --git a/src/Avalonia.Base/Media/RenderOptions.cs b/src/Avalonia.Base/Media/RenderOptions.cs index 1ac2520919..0bde05418c 100644 --- a/src/Avalonia.Base/Media/RenderOptions.cs +++ b/src/Avalonia.Base/Media/RenderOptions.cs @@ -1,13 +1,48 @@ -using Avalonia.Media.Imaging; +using System; +using Avalonia.Media.Imaging; namespace Avalonia.Media { + /// + /// Provides a set of options that control rendering behavior for visuals, including text rendering, bitmap + /// interpolation, edge rendering, blending, and opacity handling. + /// + /// Use this structure to specify rendering preferences for visual elements. Each property + /// corresponds to a specific aspect of rendering, allowing fine-grained control over how content is displayed. + /// These options can be applied to visuals to influence quality, performance, and visual effects. When merging two + /// instances, unspecified values are inherited from the other instance, enabling layered configuration. public readonly record struct RenderOptions { + /// + /// Gets the text rendering mode used to control how text glyphs are rendered. + /// + [Obsolete("TextRenderingMode is obsolete. Use TextOptions.TextRenderingMode instead.")] + public TextRenderingMode TextRenderingMode { get; init; } + + /// + /// Gets the interpolation mode used when rendering bitmap images. + /// + /// The interpolation mode determines how bitmap images are scaled or transformed during + /// rendering. Selecting an appropriate mode can affect image quality and performance. + /// public BitmapInterpolationMode BitmapInterpolationMode { get; init; } + + /// + /// Gets the edge rendering mode used for drawing operations. + /// public EdgeMode EdgeMode { get; init; } - public TextRenderingMode TextRenderingMode { get; init; } + + /// + /// Gets the blending mode used when rendering bitmap images. + /// + /// The blending mode determines how bitmap pixels are composited with the background or + /// other images. Select an appropriate mode based on the desired visual effect, such as transparency or + /// additive blending. public BitmapBlendingMode BitmapBlendingMode { get; init; } + + /// + /// Gets a value indicating whether full opacity handling is required for the associated content. + /// public bool? RequiresFullOpacityHandling { get; init; } /// @@ -75,6 +110,7 @@ namespace Avalonia.Media /// /// The control. /// The value. + [Obsolete("TextRenderingMode is obsolete. Use TextOptions.TextRenderingMode instead.")] public static TextRenderingMode GetTextRenderingMode(Visual visual) { return visual.RenderOptions.TextRenderingMode; @@ -85,6 +121,7 @@ namespace Avalonia.Media /// /// The control. /// The value. + [Obsolete("TextRenderingMode is obsolete. Use TextOptions.TextRenderingMode instead.")] public static void SetTextRenderingMode(Visual visual, TextRenderingMode value) { visual.RenderOptions = visual.RenderOptions with { TextRenderingMode = value }; @@ -126,11 +163,15 @@ namespace Avalonia.Media edgeMode = other.EdgeMode; } +#pragma warning disable CS0618 var textRenderingMode = TextRenderingMode; +#pragma warning restore CS0618 if (textRenderingMode == TextRenderingMode.Unspecified) { +#pragma warning disable CS0618 textRenderingMode = other.TextRenderingMode; +#pragma warning disable CS0618 } var bitmapBlendingMode = BitmapBlendingMode; diff --git a/src/Avalonia.Base/Media/StreamGeometryContext.cs b/src/Avalonia.Base/Media/StreamGeometryContext.cs index c8072564b1..05c17f2b4d 100644 --- a/src/Avalonia.Base/Media/StreamGeometryContext.cs +++ b/src/Avalonia.Base/Media/StreamGeometryContext.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// of is obtained by calling /// . /// - public class StreamGeometryContext : IGeometryContext, IGeometryContext2 + public class StreamGeometryContext : IGeometryContext { private readonly IStreamGeometryContextImpl _impl; @@ -34,15 +34,13 @@ namespace Avalonia.Media _impl.SetFillRule(fillRule); } - - /// - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + /// + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked = true) { - _impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection); + _impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection, isStroked); _currentPoint = point; } - /// /// Draws an arc to the specified point using polylines, quadratic or cubic Bezier curves /// Significantly more precise when drawing elliptic arcs with extreme width:height ratios. @@ -59,34 +57,32 @@ namespace Avalonia.Media PreciseEllipticArcHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection); } - /// - public void BeginFigure(Point startPoint, bool isFilled) + public void BeginFigure(Point startPoint, bool isFilled = true) { _impl.BeginFigure(startPoint, isFilled); _currentPoint = startPoint; } /// - public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint) + public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked = true) { - _impl.CubicBezierTo(controlPoint1, controlPoint2, endPoint); + _impl.CubicBezierTo(controlPoint1, controlPoint2, endPoint, isStroked); _currentPoint = endPoint; } /// - public void QuadraticBezierTo(Point controlPoint , Point endPoint) + public void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked = true) { - _impl.QuadraticBezierTo(controlPoint , endPoint); + _impl.QuadraticBezierTo(controlPoint, endPoint, isStroked); _currentPoint = endPoint; } - /// - public void LineTo(Point endPoint) + public void LineTo(Point point, bool isStroked = true) { - _impl.LineTo(endPoint); - _currentPoint = endPoint; + _impl.LineTo(point, isStroked); + _currentPoint = point; } /// @@ -102,46 +98,5 @@ namespace Avalonia.Media { _impl.Dispose(); } - - /// - public void LineTo(Point point, bool isStroked) - { - if (_impl is IGeometryContext2 context2) - context2.LineTo(point, isStroked); - else - _impl.LineTo(point); - - _currentPoint = point; - } - - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked) - { - if (_impl is IGeometryContext2 context2) - context2.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection, isStroked); - else - _impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection); - - _currentPoint = point; - } - - public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked) - { - if (_impl is IGeometryContext2 context2) - context2.CubicBezierTo(controlPoint1, controlPoint2, endPoint, isStroked); - else - _impl.CubicBezierTo(controlPoint1, controlPoint2, endPoint); - - _currentPoint = endPoint; - } - - public void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked) - { - if (_impl is IGeometryContext2 context2) - context2.QuadraticBezierTo(controlPoint, endPoint, isStroked); - else - _impl.QuadraticBezierTo(controlPoint, endPoint); - - _currentPoint = endPoint; - } } } diff --git a/src/Avalonia.Base/Media/TextHintingMode.cs b/src/Avalonia.Base/Media/TextHintingMode.cs new file mode 100644 index 0000000000..0095bf04e9 --- /dev/null +++ b/src/Avalonia.Base/Media/TextHintingMode.cs @@ -0,0 +1,31 @@ +namespace Avalonia.Media +{ + /// + /// Specifies the level of hinting applied to text glyphs during rendering. + /// Text hinting adjusts glyph outlines to improve readability and crispness, + /// especially at small font sizes or low DPI. This enum controls the amount + /// of grid-fitting and outline adjustment performed. + /// + public enum TextHintingMode : byte + { + /// + /// Hinting mode is not explicitly specified. The default will be used. + /// + Unspecified, + + /// + /// No hinting, outlines are scaled only. + /// + None, + + /// + /// Minimal hinting, preserves glyph shape. + /// + Light, + + /// + /// Aggressive grid-fitting, maximum crispness at low DPI. + /// + Strong + } +} diff --git a/src/Avalonia.Base/Media/TextOptions.cs b/src/Avalonia.Base/Media/TextOptions.cs new file mode 100644 index 0000000000..d8d33b5f16 --- /dev/null +++ b/src/Avalonia.Base/Media/TextOptions.cs @@ -0,0 +1,136 @@ +namespace Avalonia.Media +{ + /// + /// Provides options for controlling text rendering behavior, including rendering mode, hinting mode, and baseline + /// pixel alignment. Used to configure how text appears within visual elements. + /// + /// TextOptions encapsulates settings that influence the clarity, sharpness, and positioning of + /// rendered text. These options can be applied to visual elements to customize text appearance for different + /// display scenarios, such as optimizing for readability at small font sizes or ensuring pixel-perfect alignment. + /// The struct supports merging with other instances to inherit unspecified values, and exposes attached properties + /// for use with visuals. + public readonly record struct TextOptions + { + /// + /// Gets the text rendering mode used to control how text glyphs are rendered. + /// + public TextRenderingMode TextRenderingMode { get; init; } + + /// + /// Gets the text rendering hinting mode used to optimize the display of text. + /// + /// The hinting mode determines how text is rendered to improve clarity and readability, + /// especially at small font sizes. Changing this value may affect the appearance of text depending on the + /// rendering engine and display device. + public TextHintingMode TextHintingMode { get; init; } + + /// + /// Gets a value indicating whether the text baseline should be aligned to the pixel grid. + /// + /// + /// When enabled, the vertical position of the text baseline is snapped to whole pixel boundaries. + /// This ensures consistent sharpness and reduces blurriness caused by fractional positioning, + /// particularly at small font sizes or low DPI settings. + /// + public BaselinePixelAlignment BaselinePixelAlignment { get; init; } + + /// + /// Merges this instance with using inheritance semantics: unspecified values on this + /// instance are taken from . + /// + public TextOptions MergeWith(TextOptions other) + { + var textRenderingMode = TextRenderingMode; + + if (textRenderingMode == TextRenderingMode.Unspecified) + { + textRenderingMode = other.TextRenderingMode; + } + + var textHintingMode = TextHintingMode; + + if (textHintingMode == TextHintingMode.Unspecified) + { + textHintingMode = other.TextHintingMode; + } + + var baselinePixelAlignment = BaselinePixelAlignment; + + if (baselinePixelAlignment == BaselinePixelAlignment.Unspecified) + { + baselinePixelAlignment = other.BaselinePixelAlignment; + } + + return new TextOptions + { + TextRenderingMode = textRenderingMode, + TextHintingMode = textHintingMode, + BaselinePixelAlignment = baselinePixelAlignment + }; + } + + /// + /// Gets the TextOptions attached value for a visual. + /// + public static TextOptions GetTextOptions(Visual visual) + { + return visual.TextOptions; + } + + /// + /// Sets the TextOptions attached value for a visual. + /// + public static void SetTextOptions(Visual visual, TextOptions value) + { + visual.TextOptions = value; + } + + /// + /// Gets the TextRenderingMode attached property for a visual. + /// + public static TextRenderingMode GetTextRenderingMode(Visual visual) + { + return visual.TextOptions.TextRenderingMode; + } + + /// + /// Sets the TextRenderingMode attached property for a visual. + /// + public static void SetTextRenderingMode(Visual visual, TextRenderingMode value) + { + visual.TextOptions = visual.TextOptions with { TextRenderingMode = value }; + } + + /// + /// Gets the TextHintingMode attached property for a visual. + /// + public static TextHintingMode GetTextHintingMode(Visual visual) + { + return visual.TextOptions.TextHintingMode; + } + + /// + /// Sets the TextHintingMode attached property for a visual. + /// + public static void SetTextHintingMode(Visual visual, TextHintingMode value) + { + visual.TextOptions = visual.TextOptions with { TextHintingMode = value }; + } + + /// + /// Gets the BaselinePixelAlignment attached property for a visual. + /// + public static BaselinePixelAlignment GetBaselinePixelAlignment(Visual visual) + { + return visual.TextOptions.BaselinePixelAlignment; + } + + /// + /// Sets the BaselinePixelAlignment attached property for a visual. + /// + public static void SetBaselinePixelAlignment(Visual visual, BaselinePixelAlignment value) + { + visual.TextOptions = visual.TextOptions with { BaselinePixelAlignment = value }; + } + } +} diff --git a/src/Avalonia.Base/Media/TextRenderingMode.cs b/src/Avalonia.Base/Media/TextRenderingMode.cs index 927d2bce73..dfc084720f 100644 --- a/src/Avalonia.Base/Media/TextRenderingMode.cs +++ b/src/Avalonia.Base/Media/TextRenderingMode.cs @@ -1,11 +1,36 @@ namespace Avalonia.Media { + /// + /// Specifies how text glyphs are rendered in Avalonia. + /// Controls the smoothing and antialiasing applied during text rasterization. + /// public enum TextRenderingMode : byte { + /// + /// Rendering mode is not explicitly specified. + /// The system or platform default will be used. + /// Unspecified, - SubpixelAntialias, + /// + /// Glyphs are rendered with subpixel antialiasing. + /// This provides higher apparent resolution on LCD screens + /// by using the individual red, green, and blue subpixels. + /// + SubpixelAntialias, + + /// + /// Glyphs are rendered with standard grayscale antialiasing. + /// This smooths edges without using subpixel information, + /// preserving shape fidelity across different display types. + /// Antialias, + + /// + /// Glyphs are rendered without antialiasing. + /// This produces sharp, aliased edges and may be useful + /// for pixel-art aesthetics or low-DPI environments. + /// Alias } } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index 1a813f7bbe..848620dae2 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -195,6 +195,17 @@ namespace Avalonia.Platform /// void PopRenderOptions(); + /// + /// Pushes text options for the drawing context. + /// + /// The text options. + void PushTextOptions(TextOptions textOptions); + + /// + /// Pops the latest text options. + /// + void PopTextOptions(); + /// /// Attempts to get an optional feature from the drawing context implementation. /// diff --git a/src/Avalonia.Base/Platform/IGeometryContext.cs b/src/Avalonia.Base/Platform/IGeometryContext.cs index 614b331761..4305dccd3f 100644 --- a/src/Avalonia.Base/Platform/IGeometryContext.cs +++ b/src/Avalonia.Base/Platform/IGeometryContext.cs @@ -18,7 +18,8 @@ namespace Avalonia.Platform /// /// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction. /// - void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection); + /// Whether the segment is stroked + void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked = true); /// /// Begins a new figure. @@ -33,20 +34,23 @@ namespace Avalonia.Platform /// The first control point used to specify the shape of the curve. /// The second control point used to specify the shape of the curve. /// The destination point for the end of the curve. - void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint); + /// Whether the segment is stroked + void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked = true); /// /// Draws a quadratic Bezier curve to the specified point /// - /// Control point - /// DestinationPoint - void QuadraticBezierTo(Point controlPoint , Point endPoint); + /// The control point used to specify the shape of the curve. + /// The destination point for the end of the curve. + /// Whether the segment is stroked + void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked = true); /// /// Draws a line to the specified point. /// - /// The destination point. - void LineTo(Point endPoint); + /// The destination point. + /// Whether the segment is stroked + void LineTo(Point point, bool isStroked = true); /// /// Ends the figure started by . diff --git a/src/Avalonia.Base/Platform/IGeometryContext2.cs b/src/Avalonia.Base/Platform/IGeometryContext2.cs deleted file mode 100644 index 4142430e9d..0000000000 --- a/src/Avalonia.Base/Platform/IGeometryContext2.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Avalonia.Media; - -namespace Avalonia.Platform -{ - // TODO12 combine with IGeometryContext - public interface IGeometryContext2 : IGeometryContext - { - /// - /// Draws a line to the specified point. - /// - /// The destination point. - /// Whether the segment is stroked - void LineTo(Point point, bool isStroked); - - /// - /// Draws an arc to the specified point. - /// - /// The destination point. - /// The radii of an oval whose perimeter is used to draw the angle. - /// The rotation angle (in radians) of the oval that specifies the curve. - /// true to draw the arc greater than 180 degrees; otherwise, false. - /// - /// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction. - /// - /// Whether the segment is stroked - void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked); - - /// - /// Draws a Bezier curve to the specified point. - /// - /// The first control point used to specify the shape of the curve. - /// The second control point used to specify the shape of the curve. - /// The destination point for the end of the curve. - /// Whether the segment is stroked - void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked); - - /// - /// Draws a quadratic Bezier curve to the specified point - /// - /// Control point - /// DestinationPoint - /// Whether the segment is stroked - void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked); - } - -} diff --git a/src/Avalonia.Base/Platform/PathGeometryContext.cs b/src/Avalonia.Base/Platform/PathGeometryContext.cs index bcb4f2a272..a20c7cf26b 100644 --- a/src/Avalonia.Base/Platform/PathGeometryContext.cs +++ b/src/Avalonia.Base/Platform/PathGeometryContext.cs @@ -21,7 +21,7 @@ namespace Avalonia.Visuals.Platform } /// - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked = true) { var arcSegment = new ArcSegment { @@ -29,7 +29,8 @@ namespace Avalonia.Visuals.Platform RotationAngle = rotationAngle, IsLargeArc = isLargeArc, SweepDirection = sweepDirection, - Point = point + Point = point, + IsStroked = isStroked }; CurrentFigureSegments().Add(arcSegment); @@ -47,27 +48,39 @@ namespace Avalonia.Visuals.Platform } /// - public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint) + public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked = true) { - var bezierSegment = new BezierSegment { Point1 = controlPoint1, Point2 = controlPoint2, Point3 = endPoint }; + var bezierSegment = new BezierSegment + { + Point1 = controlPoint1, + Point2 = controlPoint2, + Point3 = endPoint, + IsStroked = isStroked + }; CurrentFigureSegments().Add(bezierSegment); } /// - public void QuadraticBezierTo(Point controlPoint , Point endPoint) + public void QuadraticBezierTo(Point controlPoint , Point endPoint, bool isStroked = true) { - var quadraticBezierSegment = new QuadraticBezierSegment { Point1 = controlPoint , Point2 = endPoint }; + var quadraticBezierSegment = new QuadraticBezierSegment + { + Point1 = controlPoint, + Point2 = endPoint, + IsStroked = isStroked + }; CurrentFigureSegments().Add(quadraticBezierSegment); } /// - public void LineTo(Point endPoint) + public void LineTo(Point point, bool isStroked = true) { var lineSegment = new LineSegment { - Point = endPoint + Point = point, + IsStroked = isStroked }; CurrentFigureSegments().Add(lineSegment); diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs index ac0a5c4cb7..c7d10f69a1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs @@ -232,3 +232,18 @@ class RenderDataRenderOptionsNode : RenderDataPushNode context.Context.PopRenderOptions(); } } + +class RenderDataTextOptionsNode : RenderDataPushNode +{ + public TextOptions TextOptions { get; set; } + + public override void Push(ref RenderDataNodeRenderContext context) + { + context.Context.PushTextOptions(TextOptions); + } + + public override void Pop(ref RenderDataNodeRenderContext context) + { + context.Context.PopTextOptions(); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs index 870a084d31..ead6a3f730 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs @@ -264,6 +264,10 @@ internal class RenderDataDrawingContext : DrawingContext RenderOptions = renderOptions }); + protected override void PushTextOptionsCore(TextOptions textOptions) => Push(new RenderDataTextOptionsNode() + { + TextOptions = textOptions + }); protected override void PopClipCore() => Pop(); @@ -277,6 +281,8 @@ internal class RenderDataDrawingContext : DrawingContext protected override void PopRenderOptionsCore() => Pop(); + protected override void PopTextOptionsCore() => Pop(); + internal override void DrawBitmap(IRef? source, double opacity, Rect sourceRect, Rect destRect) { if (source == null || sourceRect.IsEmpty() || destRect.IsEmpty()) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs index ee0447629a..d26aec0d69 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs @@ -22,6 +22,7 @@ internal partial class CompositorDrawingContextProxy PushOpacityMask, PushGeometryClip, PushRenderOptions, + PushTextOptions, PushEffect } @@ -43,6 +44,7 @@ internal partial class CompositorDrawingContextProxy [FieldOffset(0)] public Matrix Transform; [FieldOffset(0)] public RenderOptions RenderOptions; + [FieldOffset(0)] public TextOptions TextOptions; // PushClip/PushOpacityMask [FieldOffset(0)] public bool IsRoundRect; @@ -148,6 +150,8 @@ internal partial class CompositorDrawingContextProxy } else if (cmd.Type == PendingCommandType.PushRenderOptions) _impl.PushRenderOptions(cmd.DataUnion.RenderOptions); + else if (cmd.Type == PendingCommandType.PushTextOptions) + _impl.PushTextOptions(cmd.DataUnion.TextOptions); else Debug.Assert(false); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 6b4982c490..81041a659d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -260,6 +260,18 @@ internal partial class CompositorDrawingContextProxy : IDrawingContextImpl, }); } + public void PushTextOptions(TextOptions textOptions) + { + AddCommand(new() + { + Type = PendingCommandType.PushTextOptions, + DataUnion = + { + TextOptions = textOptions + } + }); + } + public void PopRenderOptions() { if (!TryDiscardOrFlush(PendingCommandType.PushRenderOptions)) @@ -269,6 +281,15 @@ internal partial class CompositorDrawingContextProxy : IDrawingContextImpl, } } + public void PopTextOptions() + { + if (!TryDiscardOrFlush(PendingCommandType.PushTextOptions)) + { + _impl.PopTextOptions(); + RestoreTransform(); + } + } + public object? GetFeature(Type t) { Flush(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index c2d43f5667..9225dd6ac6 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -62,6 +62,12 @@ namespace Avalonia.Rendering.Composition.Server if (applyRenderOptions) canvas.PushRenderOptions(RenderOptions); + + var applyTextOptions = TextOptions != default; + + if (applyTextOptions) + canvas.PushTextOptions(TextOptions); + var needPopEffect = PushEffect(canvas); if (Opacity != 1) @@ -88,7 +94,9 @@ namespace Avalonia.Rendering.Composition.Server if (needPopEffect) canvas.PopEffect(); - if(applyRenderOptions) + if (applyTextOptions) + canvas.PopTextOptions(); + if (applyRenderOptions) canvas.PopRenderOptions(); } diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index d308f532da..f054e77db8 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -50,6 +50,7 @@ internal class ImmediateRenderer transform = Matrix.CreateTranslation(bounds.Position); } + using (visual.TextOptions != default ? context.PushTextOptions(visual.TextOptions) : default(DrawingContext.PushedState?)) using (visual.RenderOptions != default ? context.PushRenderOptions(visual.RenderOptions) : default(DrawingContext.PushedState?)) using (context.PushTransform(transform)) using (visual.HasMirrorTransform ? context.PushTransform(new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0)) : default(DrawingContext.PushedState?)) diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs deleted file mode 100644 index e83ef8c479..0000000000 --- a/src/Avalonia.Base/Utilities/StringTokenizer.cs +++ /dev/null @@ -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 CurrentTokenSpan => _tokenIndex < 0 ? ReadOnlySpan.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 result, char? separator = null) - { - var success = TryReadToken(separator ?? _separator); - result = CurrentTokenSpan; - return success; - } - - public ReadOnlySpan 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; - } - } -} diff --git a/src/Avalonia.Base/Visual.Composition.cs b/src/Avalonia.Base/Visual.Composition.cs index f521f2a3f2..a1f90a5407 100644 --- a/src/Avalonia.Base/Visual.Composition.cs +++ b/src/Avalonia.Base/Visual.Composition.cs @@ -148,6 +148,7 @@ public partial class Visual comp.Effect = Effect?.ToImmutable(); comp.RenderOptions = RenderOptions; + comp.TextOptions = TextOptions; var renderTransform = Matrix.Identity; @@ -163,4 +164,4 @@ public partial class Visual comp.TransformMatrix = renderTransform; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 6b10fa228c..ee960cee1e 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -1,5 +1,3 @@ - - #nullable enable using System; @@ -39,7 +37,7 @@ namespace Avalonia /// public static readonly DirectProperty BoundsProperty = AvaloniaProperty.RegisterDirect(nameof(Bounds), o => o.Bounds); - + /// /// Defines the property. /// @@ -51,7 +49,7 @@ namespace Avalonia /// public static readonly StyledProperty ClipProperty = AvaloniaProperty.Register(nameof(Clip)); - + /// /// Defines the property. /// @@ -69,7 +67,7 @@ namespace Avalonia /// public static readonly StyledProperty OpacityMaskProperty = AvaloniaProperty.Register(nameof(OpacityMask)); - + /// /// Defines the property. /// @@ -113,7 +111,7 @@ namespace Avalonia /// public static readonly StyledProperty ZIndexProperty = AvaloniaProperty.Register(nameof(ZIndex)); - + private static readonly WeakEvent InvalidatedWeakEvent = WeakEvent.Register( (s, h) => s.Invalidated += h, @@ -124,6 +122,8 @@ namespace Avalonia private Visual? _visualParent; private bool _hasMirrorTransform; private TargetWeakEventSubscriber? _affectsRenderWeakSubscriber; + private RenderOptions _renderOptions; + private TextOptions _textOptions; /// /// Initializes static members of the class. @@ -201,7 +201,7 @@ namespace Avalonia /// Gets a value indicating whether this control and all its parents are visible. /// public bool IsEffectivelyVisible { get; private set; } = true; - + /// /// Updates the property based on the parent's /// . @@ -218,7 +218,7 @@ namespace Avalonia // PERF-SENSITIVE: This is called on entire hierarchy and using foreach or LINQ // will cause extra allocations and overhead. - + var children = VisualChildren; // ReSharper disable once ForCanBeConvertedToForeach @@ -255,7 +255,7 @@ namespace Avalonia get { return GetValue(OpacityMaskProperty); } set { SetValue(OpacityMaskProperty, value); } } - + /// /// Gets or sets the effect of the control. /// @@ -269,8 +269,8 @@ namespace Avalonia /// /// Gets or sets a value indicating whether to apply mirror transform on this control. /// - public bool HasMirrorTransform - { + public bool HasMirrorTransform + { get { return _hasMirrorTransform; } protected set { SetAndRaise(HasMirrorTransformProperty, ref _hasMirrorTransform, value); } } @@ -326,7 +326,25 @@ namespace Avalonia /// protected internal IRenderRoot? VisualRoot => _visualRoot; - internal RenderOptions RenderOptions { get; set; } + internal RenderOptions RenderOptions + { + get => _renderOptions; + set + { + _renderOptions = value; + InvalidateVisual(); + } + } + + internal TextOptions TextOptions + { + get => _textOptions; + set + { + _textOptions = value; + InvalidateVisual(); + } + } internal bool HasNonUniformZIndexChildren { get; private set; } @@ -413,8 +431,8 @@ namespace Avalonia sender.InvalidateVisual(); } }); - - + + var invalidateAndSubscribeObserver = new AnonymousObserver( static e => { @@ -466,7 +484,7 @@ namespace Avalonia if (change.Property == IsVisibleProperty) { UpdateIsEffectivelyVisible(VisualParent?.IsEffectivelyVisible ?? true); - } + } else if (change.Property == FlowDirectionProperty) { InvalidateMirrorTransform(); @@ -477,7 +495,7 @@ namespace Avalonia } } } - + protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.LogicalChildrenCollectionChanged(sender, e); @@ -515,12 +533,12 @@ namespace Avalonia OnAttachedToVisualTree(e); AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); - + _visualRoot.Renderer.RecalculateChildren(_visualParent); - + if (ZIndex != 0) _visualParent.HasNonUniformZIndexChildren = true; - + var visualChildren = VisualChildren; var visualChildrenCount = visualChildren.Count; @@ -617,7 +635,7 @@ namespace Avalonia { newTransform.Changed += sender.RenderTransformChanged; } - + sender.InvalidateVisual(); } } @@ -651,7 +669,7 @@ namespace Avalonia var parent = sender?.VisualParent; if (sender?.ZIndex != 0 && parent is Visual parentVisual) parentVisual.HasNonUniformZIndexChildren = true; - + sender?.InvalidateVisual(); parent?.VisualRoot?.Renderer.RecalculateChildren(parent); } @@ -721,7 +739,7 @@ namespace Avalonia break; } } - + private static void SetVisualParent(IList children, Visual? parent) { var count = children.Count; @@ -729,7 +747,7 @@ namespace Avalonia for (var i = 0; i < count; i++) { var visual = (Visual) children[i]!; - + visual.SetVisualParent(parent); } } diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index ce989296b1..dd1d284cd5 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -34,6 +34,7 @@ + diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 65929f1357..bc6380a012 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -6,6 +6,7 @@ tools $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL true + true $(NoWarn);NU1605;CS8632 embedded false @@ -61,9 +62,6 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - - Markup/%(RecursiveDir)%(FileName)%(Extension) - Markup/%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index 4e11715c56..e46dcb0eb2 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -211,7 +211,7 @@ namespace Avalonia.Automation /// Defines the AutomationProperties.LiveSetting attached property. /// /// - /// This property currently has no effect. + /// This property affects the default value for and controls whether live region changed events are emitted. /// public static readonly AttachedProperty LiveSettingProperty = AvaloniaProperty.RegisterAttached( diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 65f9503061..56111bdc83 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -47,6 +47,7 @@ namespace Avalonia.Automation.Peers Table, TitleBar, Separator, + Expander, } public enum AutomationLandmarkType @@ -475,6 +476,12 @@ namespace Avalonia.Automation.Peers /// true if a context menu is present for the element; otherwise false. public bool ShowContextMenu() => ShowContextMenuCore(); + /// + /// Gets the current live setting that is associated with this this automation peer. + /// + /// The live setting to use for automation. + public AutomationLiveSetting GetLiveSetting() => GetLiveSettingCore(); + /// /// Tries to get a provider of the specified type from the peer. /// @@ -536,6 +543,7 @@ namespace Avalonia.Automation.Peers AutomationControlType.SplitButton => "split button", AutomationControlType.HeaderItem => "header item", AutomationControlType.TitleBar => "title bar", + AutomationControlType.Expander => "group", AutomationControlType.None => (GetLandmarkType()?.ToString() ?? controlType.ToString()).ToLowerInvariant(), _ => controlType.ToString().ToLowerInvariant(), }; @@ -563,6 +571,7 @@ namespace Avalonia.Automation.Peers protected virtual bool IsOffscreenCore() => false; protected abstract void SetFocusCore(); protected abstract bool ShowContextMenuCore(); + protected virtual AutomationLiveSetting GetLiveSettingCore() => AutomationLiveSetting.Off; protected virtual AutomationControlType GetControlTypeOverrideCore() { diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index ef14be0169..c59ba6b148 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -185,6 +185,8 @@ namespace Avalonia.Automation.Peers return false; } + protected override AutomationLiveSetting GetLiveSettingCore() => AutomationProperties.GetLiveSetting(Owner); + protected internal override bool TrySetParent(AutomationPeer? parent) { _parent = parent; diff --git a/src/Avalonia.Controls/Automation/Peers/ExpanderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ExpanderAutomationPeer.cs new file mode 100644 index 0000000000..a23e8f045a --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ExpanderAutomationPeer.cs @@ -0,0 +1,47 @@ +using Avalonia.Automation; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; + +namespace Avalonia.Controls.Automation.Peers +{ + public class ExpanderAutomationPeer : ControlAutomationPeer, + IExpandCollapseProvider + { + public ExpanderAutomationPeer(Control owner) + : base(owner) + { + owner.PropertyChanged += OwnerPropertyChanged; + } + + public new Expander Owner => (Expander)base.Owner; + + public ExpandCollapseState ExpandCollapseState => ToState(Owner.IsExpanded); + public bool ShowsMenu => false; + public void Collapse() => Owner.IsExpanded = false; + public void Expand() => Owner.IsExpanded = true; + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Expander; + } + + private void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == Expander.IsExpandedProperty) + { + RaisePropertyChangedEvent( + ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, + ToState((bool)e.OldValue!), + ToState((bool)e.NewValue!)); + } + } + + private static ExpandCollapseState ToState(bool value) + { + return value ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; + } + + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs index bc550b6937..e1218384d3 100644 --- a/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs @@ -7,6 +7,16 @@ namespace Avalonia.Automation.Peers public TextBlockAutomationPeer(TextBlock owner) : base(owner) { + Owner.PropertyChanged += (a, e) => + { + if (e.Property == TextBlock.TextProperty) + { + RaisePropertyChangedEvent( + AutomationElementIdentifiers.NameProperty, + e.OldValue, + e.NewValue); + } + }; } public new TextBlock Owner => (TextBlock)base.Owner; diff --git a/src/Avalonia.Controls/Automation/Peers/TitleBarAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TitleBarAutomationPeer.cs index e6920c1e02..4bd606dd6e 100644 --- a/src/Avalonia.Controls/Automation/Peers/TitleBarAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TitleBarAutomationPeer.cs @@ -10,7 +10,7 @@ internal class TitleBarAutomationPeer : ControlAutomationPeer { } - protected override bool IsContentElementCore() => true; + protected override bool IsContentElementCore() => false; protected override string GetClassNameCore() { diff --git a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs index 979d54f48e..99b3cdd657 100644 --- a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs @@ -1,4 +1,5 @@ using Avalonia.Automation.Provider; +using Avalonia.Controls.Automation; using Avalonia.Controls.Primitives; namespace Avalonia.Automation.Peers @@ -8,19 +9,28 @@ namespace Avalonia.Automation.Peers public ToggleButtonAutomationPeer(ToggleButton owner) : base(owner) { + Owner.PropertyChanged += (a, e) => + { + if (e.Property == ToggleButton.IsCheckedProperty) + { + RaisePropertyChangedEvent( + TogglePatternIdentifiers.ToggleStateProperty, + ToState((bool?)e.OldValue), + ToState((bool?)e.NewValue)); + } + }; } public new ToggleButton Owner => (ToggleButton)base.Owner; - ToggleState IToggleProvider.ToggleState + private ToggleState ToState(bool? value) => value switch { - get => Owner.IsChecked switch - { - true => ToggleState.On, - false => ToggleState.Off, - null => ToggleState.Indeterminate, - }; - } + true => ToggleState.On, + false => ToggleState.Off, + null => ToggleState.Indeterminate, + }; + + ToggleState IToggleProvider.ToggleState => ToState(Owner.IsChecked); void IToggleProvider.Toggle() { diff --git a/src/Avalonia.Controls/Automation/TogglePatternIdentifiers.cs b/src/Avalonia.Controls/Automation/TogglePatternIdentifiers.cs new file mode 100644 index 0000000000..be6b1c8fb2 --- /dev/null +++ b/src/Avalonia.Controls/Automation/TogglePatternIdentifiers.cs @@ -0,0 +1,15 @@ +using Avalonia.Automation.Provider; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as identifiers by . + /// + public static class TogglePatternIdentifiers + { + /// + /// Identifies the property. + /// + public static AutomationProperty ToggleStateProperty { get; } = new AutomationProperty(); + } +} diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs index 9d6bb93ebb..1e7912d75f 100644 --- a/src/Avalonia.Controls/Design.cs +++ b/src/Avalonia.Controls/Design.cs @@ -127,22 +127,6 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty PreviewWithProperty = AvaloniaProperty .RegisterAttached("PreviewWith", typeof (Design)); - - /// - /// Sets a preview template for the specified at design-time. - /// - /// - /// This method allows you to specify a substitute control to be rendered in the previewer - /// for a given object. - /// - /// The target object. - /// The preview control. - // TODO12: Remove this overload in Avalonia 12 - [Obsolete("Use SetPreviewWith(AvaloniaObject, ITemplate) overload instead. Use from XAML")] - public static void SetPreviewWith(AvaloniaObject target, Control? control) - { - s_previewWith[target] = control is not null ? new FuncTemplate(() => control) : null; - } /// /// Sets a preview template for the specified at design-time. diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs index 1ededcf81c..fe526e9204 100644 --- a/src/Avalonia.Controls/Documents/Inline.cs +++ b/src/Avalonia.Controls/Documents/Inline.cs @@ -10,11 +10,10 @@ namespace Avalonia.Controls.Documents /// public abstract class Inline : TextElement { - // TODO12: change the field type to an AttachedProperty for consistency (breaking change) /// /// AvaloniaProperty for property. /// - public static readonly StyledProperty TextDecorationsProperty = + public static readonly AttachedProperty TextDecorationsProperty = AvaloniaProperty.RegisterAttached( nameof(TextDecorations), inherits: true); diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index aeee9f07ea..eb10516a82 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -1,6 +1,8 @@ using System; using System.Threading; using Avalonia.Animation; +using Avalonia.Automation.Peers; +using Avalonia.Controls.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; @@ -274,6 +276,11 @@ namespace Avalonia.Controls } } + protected override AutomationPeer OnCreateAutomationPeer() + { + return new ExpanderAutomationPeer(this); + } + /// /// Updates the visual state of the control by applying latest PseudoClasses. /// diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index 4622fee005..c3b62860c7 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -1,8 +1,5 @@ using System; using System.ComponentModel; -using System.Runtime.CompilerServices; -using Avalonia.Diagnostics; -using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Platform @@ -45,7 +42,7 @@ namespace Avalonia.Platform /// /// Represents a single display screen. /// - public class Screen : IEquatable + public abstract class Screen : IEquatable { /// /// Gets the device name associated with a display. @@ -96,22 +93,6 @@ namespace Avalonia.Platform [Obsolete("Use the IsPrimary property instead.", true), EditorBrowsable(EditorBrowsableState.Never)] public bool Primary => IsPrimary; - /// - /// Initializes a new instance of the class. - /// - /// The scaling factor applied to the screen by the operating system. - /// The overall pixel-size of the screen. - /// The actual working-area pixel-size of the screen. - /// Whether the screen is the primary one. - [Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)] - public Screen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary) - { - Scaling = scaling; - Bounds = bounds; - WorkingArea = workingArea; - IsPrimary = isPrimary; - } - private protected Screen() { } /// @@ -123,19 +104,15 @@ namespace Avalonia.Platform /// public virtual IPlatformHandle? TryGetPlatformHandle() => null; - // TODO12: make abstract /// - public override int GetHashCode() - => RuntimeHelpers.GetHashCode(this); + public abstract override int GetHashCode(); /// public override bool Equals(object? obj) => obj is Screen other && Equals(other); - // TODO12: make abstract /// - public virtual bool Equals(Screen? other) - => ReferenceEquals(this, other); + public abstract bool Equals(Screen? other); public static bool operator ==(Screen? left, Screen? right) { diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 3b9f0b08d3..2d775e2be0 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -45,35 +45,35 @@ namespace Avalonia.Controls.Presenters /// Gets the owner . /// internal ItemsControl? ItemsControl { get; private set; } + + private bool CanHorizontallyScroll + => _logicalScrollable?.CanHorizontallyScroll ?? false; - bool ILogicalScrollable.CanHorizontallyScroll + bool ILogicalScrollable.CanHorizontallyScroll { - get => _logicalScrollable?.CanHorizontallyScroll ?? false; - set - { - if (_logicalScrollable is not null) - _logicalScrollable.CanHorizontallyScroll = value; - } + get => CanHorizontallyScroll; + set => _logicalScrollable?.CanHorizontallyScroll = value; } - bool ILogicalScrollable.CanVerticallyScroll + bool IScrollable.CanHorizontallyScroll + => CanHorizontallyScroll; + + private bool CanVerticallyScroll + => _logicalScrollable?.CanVerticallyScroll ?? false; + + bool ILogicalScrollable.CanVerticallyScroll { - get => _logicalScrollable?.CanVerticallyScroll ?? false; - set - { - if (_logicalScrollable is not null) - _logicalScrollable.CanVerticallyScroll = value; - } + get => CanVerticallyScroll; + set => _logicalScrollable?.CanVerticallyScroll = value; } + bool IScrollable.CanVerticallyScroll + => CanVerticallyScroll; + Vector IScrollable.Offset { get => _logicalScrollable?.Offset ?? default; - set - { - if (_logicalScrollable is not null) - _logicalScrollable.Offset = value; - } + set => _logicalScrollable?.Offset = value; } bool ILogicalScrollable.IsLogicalScrollEnabled => _logicalScrollable?.IsLogicalScrollEnabled ?? false; diff --git a/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs b/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs index e0c6241620..6c96287c10 100644 --- a/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs +++ b/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs @@ -19,12 +19,12 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets a value indicating whether the content can be scrolled horizontally. /// - bool CanHorizontallyScroll { get; set; } + new bool CanHorizontallyScroll { get; set; } /// /// Gets or sets a value indicating whether the content can be scrolled horizontally. /// - bool CanVerticallyScroll { get; set; } + new bool CanVerticallyScroll { get; set; } /// /// Gets a value indicating whether logical scrolling is enabled on the control. diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs index 5099567630..aa83266683 100644 --- a/src/Avalonia.Controls/Primitives/TextSearch.cs +++ b/src/Avalonia.Controls/Primitives/TextSearch.cs @@ -24,22 +24,20 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty TextBindingProperty = AvaloniaProperty.RegisterAttached("TextBinding", typeof(TextSearch)); - // TODO12: Control should be Interactive to match the property definition. /// /// Sets the value of the attached property to a given . /// /// The control. /// The search text to set. - 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. /// /// Gets the value of the attached property from a given . /// /// The control. /// The search text. - public static string? GetText(Control control) + public static string? GetText(Interactive control) => control.GetValue(TextProperty); /// diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 3152eec2db..e93180fff4 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// [TemplatePart("PART_HorizontalScrollBar", typeof(ScrollBar))] [TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))] - public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider, IInternalScroller + public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider { /// /// Defines the property. @@ -284,7 +284,7 @@ namespace Avalonia.Controls get => HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled; } - bool IInternalScroller.CanHorizontallyScroll => CanHorizontallyScroll; + bool IScrollable.CanHorizontallyScroll => CanHorizontallyScroll; /// /// Gets a value indicating whether the viewer can scroll vertically. @@ -294,7 +294,7 @@ namespace Avalonia.Controls get => VerticalScrollBarVisibility != ScrollBarVisibility.Disabled; } - bool IInternalScroller.CanVerticallyScroll => CanVerticallyScroll; + bool IScrollable.CanVerticallyScroll => CanVerticallyScroll; /// public Control? CurrentAnchor => (Presenter as IScrollAnchorProvider)?.CurrentAnchor; diff --git a/src/Avalonia.Controls/VirtualizingCarouselPanel.cs b/src/Avalonia.Controls/VirtualizingCarouselPanel.cs index 8b67f01fb1..454069b4b2 100644 --- a/src/Avalonia.Controls/VirtualizingCarouselPanel.cs +++ b/src/Avalonia.Controls/VirtualizingCarouselPanel.cs @@ -29,9 +29,23 @@ namespace Avalonia.Controls private int _transitionFromIndex = -1; private CancellationTokenSource? _transition; private EventHandler? _scrollInvalidated; + private bool _canHorizontallyScroll; + private bool _canVerticallyScroll; - bool ILogicalScrollable.CanHorizontallyScroll { get; set; } - bool ILogicalScrollable.CanVerticallyScroll { get; set; } + bool ILogicalScrollable.CanHorizontallyScroll + { + get => _canHorizontallyScroll; + set => _canHorizontallyScroll = value; + } + + bool ILogicalScrollable.CanVerticallyScroll + { + get => _canVerticallyScroll; + set => _canVerticallyScroll = value; + } + + bool IScrollable.CanHorizontallyScroll => _canHorizontallyScroll; + bool IScrollable.CanVerticallyScroll => _canVerticallyScroll; bool ILogicalScrollable.IsLogicalScrollEnabled => true; Size ILogicalScrollable.ScrollSize => new(1, 1); Size ILogicalScrollable.PageScrollSize => new(1, 1); diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index e178febd42..86e0c5453d 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -13,6 +13,14 @@ namespace Avalonia.Native { internal class AvnAutomationPeer : NativeCallbackBase, IAvnAutomationPeer { + private static readonly Dictionary s_propertyMap = new() + { + { AutomationElementIdentifiers.BoundingRectangleProperty, AvnAutomationProperty.AutomationPeer_BoundingRectangle }, + { AutomationElementIdentifiers.ClassNameProperty, AvnAutomationProperty.AutomationPeer_ClassName }, + { AutomationElementIdentifiers.NameProperty, AvnAutomationProperty.AutomationPeer_Name }, + { RangeValuePatternIdentifiers.ValueProperty, AvnAutomationProperty.RangeValueProvider_Value }, + }; + private static readonly ConditionalWeakTable s_wrappers = new(); private readonly AutomationPeer _inner; @@ -20,6 +28,7 @@ namespace Avalonia.Native { _inner = inner; _inner.ChildrenChanged += (_, _) => Node?.ChildrenChanged(); + _inner.PropertyChanged += OnPeerPropertyChanged; if (inner is IRootProvider root) root.FocusChanged += (_, _) => Node?.FocusChanged(); } @@ -41,6 +50,7 @@ namespace Avalonia.Native public int HeadingLevel => _inner.GetHeadingLevel(); public IAvnAutomationPeer? Parent => Wrap(_inner.GetParent()); public IAvnAutomationPeer? VisualRoot => Wrap(_inner.GetVisualRoot()); + public AvnLiveSetting LiveSetting => (AvnLiveSetting)_inner.GetLiveSetting(); public int HasKeyboardFocus() => _inner.HasKeyboardFocus().AsComBool(); public int IsContentElement() => _inner.IsContentElement().AsComBool(); @@ -186,6 +196,12 @@ namespace Avalonia.Native } private int IsProvider() => (_inner.GetProvider() is not null).AsComBool(); + + private void OnPeerPropertyChanged(object? sender, AutomationPropertyChangedEventArgs e) + { + if (s_propertyMap.TryGetValue(e.Property, out var property)) + Node?.PropertyChanged(property); + } } internal class AvnAutomationPeerArray : NativeCallbackBase, IAvnAutomationPeerArray diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 2fff3f3794..7aabeceb72 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -652,6 +652,7 @@ enum AvnAutomationControlType AutomationTable, AutomationTitleBar, AutomationSeparator, + AutomationExpander, } enum AvnLandmarkType @@ -688,6 +689,13 @@ enum AvnPointerDeviceType Pen, } +enum AvnLiveSetting +{ + LiveSettingOff, + LiveSettingPolite, + LiveSettingAssertive, +} + [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { @@ -1323,6 +1331,8 @@ interface IAvnAutomationPeer : IUnknown AvnLandmarkType GetLandmarkType(); int GetHeadingLevel(); + + AvnLiveSetting GetLiveSetting(); } [uuid(b00af5da-78af-4b33-bfff-4ce13a6239a9)] diff --git a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml index 3196d262a1..462dfe0323 100644 --- a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml @@ -66,7 +66,8 @@ BorderThickness="0" Background="Transparent" ItemTemplate="{TemplateBinding ItemTemplate}" - Margin="{DynamicResource AutoCompleteListPadding}" /> + Margin="{DynamicResource AutoCompleteListPadding}" + ScrollViewer.IsScrollChainingEnabled="False" /> diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 255cf29133..bcba646461 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -172,6 +172,7 @@ HorizontalAlignment="Stretch" CornerRadius="{DynamicResource OverlayCornerRadius}"> + + @@ -91,8 +93,8 @@ BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" IsEnabled="{TemplateBinding IsEnabled}" - MinWidth="{DynamicResource DatePickerThemeMinWidth}" - MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" + MinWidth="{TemplateBinding MinWidth}" + MaxWidth="{TemplateBinding MaxWidth}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TemplatedControl.IsTemplateFocusTarget="True"> diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml index 9dd65b1349..8badfa98eb 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml @@ -21,6 +21,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml index 598ec994fc..3b1b5f6c3d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml @@ -7,6 +7,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index e818b4c382..cd3942738c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -9,6 +9,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml index 1107bf2c27..8af22dd0b9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml @@ -137,7 +137,8 @@ Grid.Row="0" Focusable="False" MinWidth="{DynamicResource ScrollBarSize}" - Height="{DynamicResource ScrollBarSize}"> + Height="{DynamicResource ScrollBarSize}" + AutomationProperties.Name="Line up"> @@ -155,19 +156,22 @@ + Focusable="False" + AutomationProperties.Name="Page up"/> + Focusable="False" + AutomationProperties.Name="Page down"/> + RenderTransformOrigin="100%,50%" + AutomationProperties.Name="Position"/> + Height="{DynamicResource ScrollBarSize}" + AutomationProperties.Name="Line down"> @@ -215,7 +220,8 @@ Grid.Column="0" Focusable="False" MinHeight="{DynamicResource ScrollBarSize}" - Width="{DynamicResource ScrollBarSize}"> + Width="{DynamicResource ScrollBarSize}" + AutomationProperties.Name="Column left"> @@ -232,19 +238,22 @@ + Focusable="False" + AutomationProperties.Name="Page left"/> + Focusable="False" + AutomationProperties.Name="Page right"/> + RenderTransformOrigin="50%,100%" + AutomationProperties.Name="Position"/> + Width="{DynamicResource ScrollBarSize}" + AutomationProperties.Name="Column right"> diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml index 7eaa4bb307..27e52fa152 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml @@ -78,6 +78,8 @@ + + @@ -90,8 +92,8 @@ BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" IsEnabled="{TemplateBinding IsEnabled}" - MinWidth="{DynamicResource TimePickerThemeMinWidth}" - MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" + MinWidth="{TemplateBinding MinWidth}" + MaxWidth="{TemplateBinding MaxWidth}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> diff --git a/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml index 10b48a12f6..5b760ba14e 100644 --- a/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml @@ -38,6 +38,7 @@ BorderThickness="0" Foreground="{TemplateBinding Foreground}" ItemTemplate="{TemplateBinding ItemTemplate}" + ScrollViewer.IsScrollChainingEnabled="False" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" /> diff --git a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml index 54e75db0a2..5a6266af6e 100644 --- a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml @@ -87,7 +87,8 @@ BorderThickness="1"> + IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}" + ScrollViewer.IsScrollChainingEnabled="False"> diff --git a/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml b/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml index c70721aac7..6e2f41cf2c 100644 --- a/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml +++ b/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml @@ -6,6 +6,9 @@ + + + diff --git a/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml index aaf51a2cef..d4e8d252eb 100644 --- a/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml @@ -8,6 +8,9 @@ + + + diff --git a/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml index e8dfd3736f..a7f11ec24c 100644 --- a/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml @@ -18,7 +18,8 @@ MinWidth="{DynamicResource ScrollBarThickness}" VerticalAlignment="Center" Classes="repeat" - Focusable="False"> + Focusable="False" + AutomationProperties.Name="Column left"> + Focusable="False" + AutomationProperties.Name="Page left"/> + Focusable="False" + AutomationProperties.Name="Page right"/> - + + Focusable="False" + AutomationProperties.Name="Column right"> @@ -68,7 +73,8 @@ MinHeight="{DynamicResource ScrollBarThickness}" HorizontalAlignment="Center" Classes="repeat" - Focusable="False"> + Focusable="False" + AutomationProperties.Name="Line up"> + Focusable="False" + AutomationProperties.Name="Page up"/> + Focusable="False" + AutomationProperties.Name="Page down"/> - + + Focusable="False" + AutomationProperties.Name="Line down"> diff --git a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml index ea62f757c5..f691ff6651 100644 --- a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml @@ -40,6 +40,7 @@ + diff --git a/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs b/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs index f854e922f8..91495ea0ea 100644 --- a/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs +++ b/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 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)); - -#pragma warning restore IL2075 - RunAsync = (Func, Task>) Delegate.CreateDelegate(typeof(Func, Task>), m); - - } - - public static Func, 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 body); } diff --git a/src/HarfBuzz/Avalonia.HarfBuzz/Avalonia.HarfBuzz.csproj b/src/HarfBuzz/Avalonia.HarfBuzz/Avalonia.HarfBuzz.csproj index a528326db9..788886f3a9 100644 --- a/src/HarfBuzz/Avalonia.HarfBuzz/Avalonia.HarfBuzz.csproj +++ b/src/HarfBuzz/Avalonia.HarfBuzz/Avalonia.HarfBuzz.csproj @@ -4,8 +4,6 @@ true true true - - $(WarningsAsErrors);CS0618 diff --git a/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj b/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj index 12f890c744..4ed3d6674e 100644 --- a/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj +++ b/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj @@ -5,8 +5,7 @@ - - + diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaDelayEnumeratedTheoryTestCase.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaDelayEnumeratedTheoryTestCase.cs new file mode 100644 index 0000000000..f2f9bb3b38 --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaDelayEnumeratedTheoryTestCase.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Threading; +using Xunit.Sdk; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +internal sealed class AvaloniaDelayEnumeratedTheoryTestCase + : XunitDelayEnumeratedTheoryTestCase, ISelfExecutingXunitTestCase +{ + public AvaloniaDelayEnumeratedTheoryTestCase( + IXunitTestMethod testMethod, + string testCaseDisplayName, + string uniqueID, + bool @explicit, + bool skipTestWithoutData, + Type[]? skipExceptions = null, + string? skipReason = null, + Type? skipType = null, + string? skipUnless = null, + string? skipWhen = null, + Dictionary>? traits = null, + string? sourceFilePath = null, + int? sourceLineNumber = null, + int? timeout = null) + : base( + testMethod, + testCaseDisplayName, + uniqueID, + @explicit, + skipTestWithoutData, + skipExceptions, + skipReason, + skipType, + skipUnless, + skipWhen, + traits, + sourceFilePath, + sourceLineNumber, + timeout) + { + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public AvaloniaDelayEnumeratedTheoryTestCase() + { + } + + public async ValueTask Run( + ExplicitOption explicitOption, + IMessageBus messageBus, + object?[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + { + var tests = await aggregator.RunAsync(CreateTests, []); + + // We need to block the XUnit thread to ensure its concurrency throttle is effective. + // See https://github.com/AArnott/Xunit.StaFact/pull/55#issuecomment-826187354 for details. + var runSummary = Task.Run(async () => await AvaloniaTestCaseRunner.Instance.Run( + this, + tests, + messageBus, + aggregator, + cancellationTokenSource, + TestCaseDisplayName, + SkipReason, + explicitOption, + constructorArguments)) + .GetAwaiter() + .GetResult(); + + return runSummary; + } +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaFact.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaFact.cs index f501fc7a56..08b0984821 100644 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaFact.cs +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaFact.cs @@ -1,9 +1,7 @@ using System; -using System.ComponentModel; -using System.Threading; +using System.Runtime.CompilerServices; using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; +using Xunit.v3; namespace Avalonia.Headless.XUnit; @@ -12,24 +10,8 @@ namespace Avalonia.Headless.XUnit; /// such that awaited expressions resume on the test's "main thread". /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -[XunitTestCaseDiscoverer("Avalonia.Headless.XUnit.AvaloniaUIFactDiscoverer", "Avalonia.Headless.XUnit")] -public sealed class AvaloniaFactAttribute : FactAttribute -{ - -} - -[EditorBrowsable(EditorBrowsableState.Never)] -public class AvaloniaUIFactDiscoverer : FactDiscoverer -{ - private readonly IMessageSink diagnosticMessageSink; - public AvaloniaUIFactDiscoverer(IMessageSink diagnosticMessageSink) - : base(diagnosticMessageSink) - { - this.diagnosticMessageSink = diagnosticMessageSink; - } - - protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - return new AvaloniaTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod); - } -} +[XunitTestCaseDiscoverer(typeof(AvaloniaFactDiscoverer))] +public sealed class AvaloniaFactAttribute( + [CallerFilePath] string? sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = -1) + : FactAttribute(sourceFilePath, sourceLineNumber); diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaFactDiscoverer.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaFactDiscoverer.cs new file mode 100644 index 0000000000..5c8e971e18 --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaFactDiscoverer.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel; +using Xunit.Internal; +using Xunit.Sdk; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +[EditorBrowsable(EditorBrowsableState.Never)] +public class AvaloniaFactDiscoverer : FactDiscoverer +{ + protected override IXunitTestCase CreateTestCase( + ITestFrameworkDiscoveryOptions discoveryOptions, + IXunitTestMethod testMethod, + IFactAttribute factAttribute) + { + var details = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, factAttribute); + + return new AvaloniaTestCase( + details.ResolvedTestMethod, + details.TestCaseDisplayName, + details.UniqueID, + details.Explicit, + details.SkipExceptions, + details.SkipReason, + details.SkipType, + details.SkipUnless, + details.SkipWhen, + testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), + sourceFilePath: details.SourceFilePath, + sourceLineNumber: details.SourceLineNumber, + timeout: details.Timeout); + } +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestAssemblyRunner.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestAssemblyRunner.cs deleted file mode 100644 index 4b1cf84914..0000000000 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestAssemblyRunner.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Avalonia.Threading; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Avalonia.Headless.XUnit; - -internal class AvaloniaTestAssemblyRunner : XunitTestAssemblyRunner -{ - private HeadlessUnitTestSession? _session; - - public AvaloniaTestAssemblyRunner(ITestAssembly testAssembly, IEnumerable testCases, - IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) : base(testAssembly, testCases, diagnosticMessageSink, - executionMessageSink, executionOptions) - { - } - - protected override void SetupSyncContext(int maxParallelThreads) - { - _session = HeadlessUnitTestSession.GetOrStartForAssembly( - Assembly.Load(new AssemblyName(TestAssembly.Assembly.Name))); - base.SetupSyncContext(1); - } - - public override void Dispose() - { - _session?.Dispose(); - base.Dispose(); - } - - protected override Task RunTestCollectionAsync( - IMessageBus messageBus, - ITestCollection testCollection, - IEnumerable testCases, - CancellationTokenSource cancellationTokenSource) - { - return new AvaloniaTestCollectionRunner(_session!, testCollection, testCases, DiagnosticMessageSink, messageBus, - TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync(); - } - - private class AvaloniaTestCollectionRunner : XunitTestCollectionRunner - { - private readonly HeadlessUnitTestSession _session; - - public AvaloniaTestCollectionRunner(HeadlessUnitTestSession session, - ITestCollection testCollection, IEnumerable testCases, - IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, - ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCollection, - testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource) - { - _session = session; - } - - protected override Task RunTestClassAsync( - ITestClass testClass, - IReflectionTypeInfo @class, - IEnumerable testCases) - { - return new AvaloniaTestClassRunner(_session, testClass, @class, testCases, DiagnosticMessageSink, MessageBus, - TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, - CollectionFixtureMappings).RunAsync(); - } - } - - private class AvaloniaTestClassRunner : XunitTestClassRunner - { - private readonly HeadlessUnitTestSession _session; - - public AvaloniaTestClassRunner(HeadlessUnitTestSession session, ITestClass testClass, - IReflectionTypeInfo @class, - IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, - ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource, IDictionary collectionFixtureMappings) : - base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, - cancellationTokenSource, collectionFixtureMappings) - { - _session = session; - } - - protected override Task RunTestMethodAsync( - ITestMethod testMethod, - IReflectionMethodInfo method, - IEnumerable testCases, - object[] constructorArguments) - { - return new AvaloniaTestMethodRunner(_session, testMethod, Class, method, testCases, DiagnosticMessageSink, - MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource, - constructorArguments).RunAsync(); - } - } - - private class AvaloniaTestMethodRunner : XunitTestMethodRunner - { - private readonly HeadlessUnitTestSession _session; - private readonly IMessageBus _messageBus; - private readonly ExceptionAggregator _aggregator; - private readonly CancellationTokenSource _cancellationTokenSource; - private readonly object[] _constructorArguments; - - public AvaloniaTestMethodRunner(HeadlessUnitTestSession session, ITestMethod testMethod, - IReflectionTypeInfo @class, - IReflectionMethodInfo method, IEnumerable testCases, IMessageSink diagnosticMessageSink, - IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, - object[] constructorArguments) : base(testMethod, @class, method, testCases, diagnosticMessageSink, - messageBus, aggregator, cancellationTokenSource, constructorArguments) - { - _session = session; - _messageBus = messageBus; - _aggregator = aggregator; - _cancellationTokenSource = cancellationTokenSource; - _constructorArguments = constructorArguments; - } - - protected override Task RunTestCaseAsync(IXunitTestCase testCase) - { - return AvaloniaTestCaseRunner.RunTest(_session, testCase, testCase.DisplayName, testCase.SkipReason, - _constructorArguments, testCase.TestMethodArguments, _messageBus, _aggregator, - _cancellationTokenSource); - } - } -} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCase.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCase.cs index c620397dd5..a0a73c2c06 100644 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCase.cs +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCase.cs @@ -1,20 +1,46 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; -using Xunit.Abstractions; +using Avalonia.Threading; using Xunit.Sdk; +using Xunit.v3; namespace Avalonia.Headless.XUnit; -internal class AvaloniaTestCase : XunitTestCase +internal sealed class AvaloniaTestCase : XunitTestCase, ISelfExecutingXunitTestCase { public AvaloniaTestCase( - IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - ITestMethod testMethod, - object?[]? testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, testMethodArguments) + IXunitTestMethod testMethod, + string testCaseDisplayName, + string uniqueID, + bool @explicit, + Type[]? skipExceptions = null, + string? skipReason = null, + Type? skipType = null, + string? skipUnless = null, + string? skipWhen = null, + Dictionary>? traits = null, + object?[]? testMethodArguments = null, + string? sourceFilePath = null, + int? sourceLineNumber = null, + int? timeout = null) + : base( + testMethod, + testCaseDisplayName, + uniqueID, + @explicit, + skipExceptions, + skipReason, + skipType, + skipUnless, + skipWhen, + traits, + testMethodArguments, + sourceFilePath, + sourceLineNumber, + timeout) { } @@ -24,23 +50,30 @@ internal class AvaloniaTestCase : XunitTestCase { } - public override Task RunAsync( - IMessageSink diagnosticMessageSink, + public async ValueTask Run( + ExplicitOption explicitOption, IMessageBus messageBus, - object[] constructorArguments, + object?[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { - var session = HeadlessUnitTestSession.GetOrStartForAssembly(Method.ToRuntimeMethod().DeclaringType?.Assembly); + var tests = await aggregator.RunAsync(CreateTests, []); // We need to block the XUnit thread to ensure its concurrency throttle is effective. // See https://github.com/AArnott/Xunit.StaFact/pull/55#issuecomment-826187354 for details. - var runSummary = - Task.Run(() => AvaloniaTestCaseRunner.RunTest(session, this, DisplayName, SkipReason, constructorArguments, - TestMethodArguments, messageBus, aggregator, cancellationTokenSource)) + var runSummary = Task.Run(async () => await AvaloniaTestCaseRunner.Instance.Run( + this, + tests, + messageBus, + aggregator, + cancellationTokenSource, + TestCaseDisplayName, + SkipReason, + explicitOption, + constructorArguments)) .GetAwaiter() .GetResult(); - return Task.FromResult(runSummary); + return runSummary; } } diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunner.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunner.cs index 79b564f11d..7a52cf97c0 100644 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunner.cs +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunner.cs @@ -1,103 +1,59 @@ -using System; -using System.Collections.Generic; -using System.Reflection; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Avalonia.Threading; -using Xunit.Abstractions; using Xunit.Sdk; +using Xunit.v3; namespace Avalonia.Headless.XUnit; -internal class AvaloniaTestCaseRunner : XunitTestCaseRunner +internal sealed class AvaloniaTestCaseRunner + : XunitTestCaseRunnerBase { - private readonly HeadlessUnitTestSession _session; - private readonly Action? _onAfterTestInvoked; + public static AvaloniaTestCaseRunner Instance { get; } = new(); - public AvaloniaTestCaseRunner( - HeadlessUnitTestSession session, Action? onAfterTestInvoked, - IXunitTestCase testCase, string displayName, string skipReason, object[] constructorArguments, - object[] testMethodArguments, IMessageBus messageBus, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) : base(testCase, displayName, skipReason, constructorArguments, - testMethodArguments, messageBus, aggregator, cancellationTokenSource) + private AvaloniaTestCaseRunner() { - _session = session; - _onAfterTestInvoked = onAfterTestInvoked; } - public static Task RunTest(HeadlessUnitTestSession session, - IXunitTestCase testCase, string displayName, string skipReason, object[] constructorArguments, - object[] testMethodArguments, IMessageBus messageBus, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) + public async ValueTask Run( + IXunitTestCase testCase, + IReadOnlyCollection tests, + IMessageBus messageBus, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource, + string displayName, + string? skipReason, + ExplicitOption explicitOption, + object?[] constructorArguments) { - var afterTest = () => Dispatcher.UIThread.RunJobs(); - - var runner = new AvaloniaTestCaseRunner(session, afterTest, testCase, displayName, - skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource); - return runner.RunAsync(); - } - - protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, - object[] constructorArguments, - MethodInfo testMethod, object[] testMethodArguments, string skipReason, - IReadOnlyList beforeAfterAttributes, - ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) - { - return new AvaloniaTestRunner(_session, _onAfterTestInvoked, test, messageBus, testClass, constructorArguments, - testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource); - } - - private class AvaloniaTestRunner : XunitTestRunner - { - private readonly HeadlessUnitTestSession _session; - private readonly Action? _onAfterTestInvoked; - - public AvaloniaTestRunner( - HeadlessUnitTestSession session, Action? onAfterTestInvoked, - ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, - object[] testMethodArguments, string skipReason, - IReadOnlyList beforeAfterAttributes, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) : base(test, messageBus, testClass, constructorArguments, - testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource) - { - _session = session; - _onAfterTestInvoked = onAfterTestInvoked; - } - - protected override Task InvokeTestMethodAsync(ExceptionAggregator aggregator) - { - return _session.Dispatch( - () => new AvaloniaTestInvoker(_onAfterTestInvoked, Test, MessageBus, TestClass, - ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, - CancellationTokenSource).RunAsync(), - CancellationTokenSource.Token); - } + var session = HeadlessUnitTestSession.GetOrStartForAssembly(testCase.TestClass.Class.Assembly); + + await using var ctxt = new AvaloniaTestCaseRunnerContext( + testCase, + tests, + messageBus, + aggregator, + cancellationTokenSource, + displayName, + skipReason, + explicitOption, + constructorArguments, + session); + await ctxt.InitializeAsync(); + + return await Run(ctxt); } - private class AvaloniaTestInvoker : XunitTestInvoker - { - private readonly Action? _onAfterTestInvoked; - - public AvaloniaTestInvoker( - Action? onAfterTestInvoked, - ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, - object[] testMethodArguments, IReadOnlyList beforeAfterAttributes, - ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(test, messageBus, - testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, - cancellationTokenSource) - { - _onAfterTestInvoked = onAfterTestInvoked; - } - - protected override async Task AfterTestMethodInvokedAsync() - { - await base.AfterTestMethodInvokedAsync(); - - // Only here we can execute random code after the test, where exception will be properly handled by the XUnit. - if (_onAfterTestInvoked is not null) - { - Aggregator.Run(_onAfterTestInvoked); - } - } - } + protected override ValueTask RunTest( + AvaloniaTestCaseRunnerContext ctxt, + IXunitTest test) + => AvaloniaTestRunner.Instance.Run( + test, + ctxt.MessageBus, + ctxt.ConstructorArguments, + ctxt.ExplicitOption, + ctxt.Aggregator.Clone(), + ctxt.CancellationTokenSource, + ctxt.BeforeAfterTestAttributes, + ctxt.Session); } diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunnerContext.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunnerContext.cs new file mode 100644 index 0000000000..2c8decf9da --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestCaseRunnerContext.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Threading; +using Xunit.Sdk; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +internal sealed class AvaloniaTestCaseRunnerContext( + IXunitTestCase testCase, + IReadOnlyCollection tests, + IMessageBus messageBus, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource, + string displayName, + string? skipReason, + ExplicitOption explicitOption, + object?[] constructorArguments, + HeadlessUnitTestSession session) + : XunitTestCaseRunnerContext( + testCase, + tests, + messageBus, + aggregator, + cancellationTokenSource, + displayName, + skipReason, + explicitOption, + constructorArguments) +{ + public HeadlessUnitTestSession Session { get; } = session; +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs index aa9b3e7e18..61db27755b 100644 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs @@ -1,35 +1,13 @@ -using System.Collections.Generic; -using System.Reflection; -using Xunit.Abstractions; -using Xunit.Sdk; +using System.Reflection; +using Xunit.v3; namespace Avalonia.Headless.XUnit; -internal class AvaloniaTestFramework : XunitTestFramework +internal sealed class AvaloniaTestFramework : XunitTestFramework { - public AvaloniaTestFramework(IMessageSink messageSink) : base(messageSink) - { - } + protected override ITestFrameworkDiscoverer CreateDiscoverer(Assembly assembly) + => new AvaloniaTestFrameworkDiscoverer(new XunitTestAssembly(assembly, null, assembly.GetName().Version)); - protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) - => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); - - - private class Executor : XunitTestFrameworkExecutor - { - public Executor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, - IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider, - diagnosticMessageSink) - { - } - - protected override async void RunTestCases(IEnumerable testCases, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - { - using (var assemblyRunner = new AvaloniaTestAssemblyRunner( - TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, - executionOptions)) await assemblyRunner.RunAsync(); - } - } + protected override ITestFrameworkExecutor CreateExecutor(Assembly assembly) + => new AvaloniaTestFrameworkExecutor(new XunitTestAssembly(assembly, null, assembly.GetName().Version)); } diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs index bdd8f3b0ea..7e33adfbfb 100644 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs @@ -1,8 +1,5 @@ using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; +using Xunit.v3; namespace Avalonia.Headless.XUnit; @@ -12,27 +9,9 @@ namespace Avalonia.Headless.XUnit; /// /// It is an alternative to using [AvaloniaFact] or [AvaloniaTheory] attributes on every test method. /// -[TestFrameworkDiscoverer("Avalonia.Headless.XUnit.AvaloniaTestFrameworkTypeDiscoverer", "Avalonia.Headless.XUnit")] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] public sealed class AvaloniaTestFrameworkAttribute : Attribute, ITestFrameworkAttribute { -} - -/// -/// Discoverer implementation for the Avalonia testing framework. -/// -public class AvaloniaTestFrameworkTypeDiscoverer : ITestFrameworkTypeDiscoverer -{ - /// - /// Creates instance of . - /// - public AvaloniaTestFrameworkTypeDiscoverer(IMessageSink _) - { - } - - /// - public Type GetTestFrameworkType(IAttributeInfo attribute) - { - return typeof(AvaloniaTestFramework); - } + public Type FrameworkType + => typeof(AvaloniaTestFramework); } diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkDiscoverer.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkDiscoverer.cs new file mode 100644 index 0000000000..e29498452b --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkDiscoverer.cs @@ -0,0 +1,16 @@ +using Xunit; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +internal sealed class AvaloniaTestFrameworkDiscoverer : XunitTestFrameworkDiscoverer +{ + public AvaloniaTestFrameworkDiscoverer( + IXunitTestAssembly testAssembly, + IXunitTestCollectionFactory? collectionFactory = null) + : base(testAssembly, collectionFactory) + { + DiscovererTypeCache[typeof(FactAttribute)] = typeof(AvaloniaFactDiscoverer); + DiscovererTypeCache[typeof(TheoryAttribute)] = typeof(AvaloniaTheoryDiscoverer); + } +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkExecutor.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkExecutor.cs new file mode 100644 index 0000000000..348a718b4b --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkExecutor.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +internal sealed class AvaloniaTestFrameworkExecutor(IXunitTestAssembly testAssembly) + : XunitTestFrameworkExecutor(testAssembly) +{ + private readonly HeadlessUnitTestSession _session = HeadlessUnitTestSession.GetOrStartForAssembly(testAssembly.Assembly); + + protected override ITestFrameworkDiscoverer CreateDiscoverer() + => new AvaloniaTestFrameworkDiscoverer(TestAssembly); + + public override async ValueTask DisposeAsync() + { + await _session.DisposeAsync(); + await base.DisposeAsync(); + } +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs new file mode 100644 index 0000000000..ad3c33028b --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Threading; +using Xunit.Sdk; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +internal sealed class AvaloniaTestRunner : XunitTestRunnerBase +{ + public static AvaloniaTestRunner Instance { get; } = new(); + + private AvaloniaTestRunner() + { + } + + public async ValueTask Run( + IXunitTest test, + IMessageBus messageBus, + object?[] constructorArguments, + ExplicitOption explicitOption, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource, + IReadOnlyCollection beforeAfterAttributes, + HeadlessUnitTestSession session) + { + await using var ctxt = new AvaloniaTestRunnerContext( + test, + messageBus, + explicitOption, + aggregator, + cancellationTokenSource, + beforeAfterAttributes, + constructorArguments, + session + ); + await ctxt.InitializeAsync(); + + return await session.Dispatch( + async () => + { + var dispatcher = Dispatcher.UIThread; + var summary = await Run(ctxt); + dispatcher.RunJobs(); + return summary; + }, + ctxt.CancellationTokenSource.Token); + } +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunnerContext.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunnerContext.cs new file mode 100644 index 0000000000..137e8c3477 --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunnerContext.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading; +using Xunit.Sdk; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +internal sealed class AvaloniaTestRunnerContext( + IXunitTest test, + IMessageBus messageBus, + ExplicitOption explicitOption, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource, + IReadOnlyCollection beforeAfterTestAttributes, + object?[] constructorArguments, + HeadlessUnitTestSession session) + : XunitTestRunnerContext( + test, + messageBus, + explicitOption, + aggregator, + cancellationTokenSource, + beforeAfterTestAttributes, + constructorArguments) +{ + public HeadlessUnitTestSession Session { get; } = session; +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryAttribute.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryAttribute.cs index 53c997f08f..86bdf0bfae 100644 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryAttribute.cs +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryAttribute.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; +using Xunit.v3; namespace Avalonia.Headless.XUnit; @@ -12,26 +9,5 @@ namespace Avalonia.Headless.XUnit; /// such that awaited expressions resume on the test's "main thread". /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -[XunitTestCaseDiscoverer("Avalonia.Headless.XUnit.AvaloniaTheoryDiscoverer", "Avalonia.Headless.XUnit")] -public sealed class AvaloniaTheoryAttribute : TheoryAttribute -{ -} - -[EditorBrowsable(EditorBrowsableState.Never)] -public class AvaloniaTheoryDiscoverer : TheoryDiscoverer -{ - public AvaloniaTheoryDiscoverer(IMessageSink diagnosticMessageSink) - : base(diagnosticMessageSink) - { - } - - protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) - { - yield return new AvaloniaTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow); - } - - protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) - { - yield return new AvaloniaTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod); - } -} +[XunitTestCaseDiscoverer(typeof(AvaloniaTheoryDiscoverer))] +public sealed class AvaloniaTheoryAttribute : TheoryAttribute; diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryDiscoverer.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryDiscoverer.cs new file mode 100644 index 0000000000..a56891beed --- /dev/null +++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryDiscoverer.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using Xunit; +using Xunit.Internal; +using Xunit.Sdk; +using Xunit.v3; + +namespace Avalonia.Headless.XUnit; + +[EditorBrowsable(EditorBrowsableState.Never)] +public class AvaloniaTheoryDiscoverer : TheoryDiscoverer +{ + protected override ValueTask> CreateTestCasesForDataRow( + ITestFrameworkDiscoveryOptions discoveryOptions, + IXunitTestMethod testMethod, + ITheoryAttribute theoryAttribute, + ITheoryDataRow dataRow, + object?[] testMethodArguments) + { + var details = TestIntrospectionHelper.GetTestCaseDetailsForTheoryDataRow( + discoveryOptions, + testMethod, + theoryAttribute, + dataRow, + testMethodArguments); + var traits = TestIntrospectionHelper.GetTraits(testMethod, dataRow); + + var testCase = new AvaloniaTestCase( + details.ResolvedTestMethod, + details.TestCaseDisplayName, + details.UniqueID, + details.Explicit, + details.SkipExceptions, + details.SkipReason, + details.SkipType, + details.SkipUnless, + details.SkipWhen, + traits, + testMethodArguments, + sourceFilePath: details.SourceFilePath, + sourceLineNumber: details.SourceLineNumber, + timeout: details.Timeout); + + return ValueTask.FromResult>([testCase]); + } + + protected override ValueTask> CreateTestCasesForTheory( + ITestFrameworkDiscoveryOptions discoveryOptions, + IXunitTestMethod testMethod, + ITheoryAttribute theoryAttribute) + { + var details = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, theoryAttribute); + + var testCase = + details.SkipReason is not null && details.SkipUnless is null && details.SkipWhen is null + ? new AvaloniaTestCase( + details.ResolvedTestMethod, + details.TestCaseDisplayName, + details.UniqueID, + details.Explicit, + details.SkipExceptions, + details.SkipReason, + details.SkipType, + details.SkipUnless, + details.SkipWhen, + testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), + sourceFilePath: details.SourceFilePath, + sourceLineNumber: details.SourceLineNumber, + timeout: details.Timeout + ) + : (IXunitTestCase)new AvaloniaDelayEnumeratedTheoryTestCase( + details.ResolvedTestMethod, + details.TestCaseDisplayName, + details.UniqueID, + details.Explicit, + theoryAttribute.SkipTestWithoutData, + details.SkipExceptions, + details.SkipReason, + details.SkipType, + details.SkipUnless, + details.SkipWhen, + testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), + sourceFilePath: details.SourceFilePath, + sourceLineNumber: details.SourceLineNumber, + timeout: details.Timeout + ); + + return ValueTask.FromResult>([testCase]); + } +} diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryTestCase.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryTestCase.cs deleted file mode 100644 index ea7e7abee4..0000000000 --- a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTheoryTestCase.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.ComponentModel; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Avalonia.Headless.XUnit; - -internal class AvaloniaTheoryTestCase : XunitTheoryTestCase -{ - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public AvaloniaTheoryTestCase() - { - } - - public AvaloniaTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) - { - } - - public override Task RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) - { - var session = HeadlessUnitTestSession.GetOrStartForAssembly(Method.ToRuntimeMethod().DeclaringType?.Assembly); - - return AvaloniaTestCaseRunner - .RunTest(session, this, DisplayName, SkipReason, constructorArguments, - TestMethodArguments, messageBus, aggregator, cancellationTokenSource); - } -} diff --git a/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj b/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj index bbd10677e9..edd5ed183b 100644 --- a/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj +++ b/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index e2c406998a..0a29b9a5b8 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -313,25 +313,25 @@ namespace Avalonia.Headless _parent.Bounds = CalculateBounds(); } - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked = true) => Track(point); public void BeginFigure(Point startPoint, bool isFilled = true) => Track(startPoint); - public void CubicBezierTo(Point point1, Point point2, Point point3) + public void CubicBezierTo(Point point1, Point point2, Point point3, bool isStroked = true) { Track(point1); Track(point2); Track(point3); } - public void QuadraticBezierTo(Point control, Point endPoint) + public void QuadraticBezierTo(Point control, Point endPoint, bool isStroked = true) { Track(control); Track(endPoint); } - public void LineTo(Point point) => Track(point); + public void LineTo(Point point, bool isStroked = true) => Track(point); public void EndFigure(bool isClosed) { @@ -559,12 +559,22 @@ namespace Avalonia.Headless public void PushRenderOptions(RenderOptions renderOptions) { - + } public void PopRenderOptions() { - + + } + + public void PushTextOptions(TextOptions textOptions) + { + // No-op in headless stub + } + + public void PopTextOptions() + { + // No-op in headless stub } } diff --git a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs index 65f648b98c..0e5a6b0c8b 100644 --- a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs +++ b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs @@ -18,7 +18,7 @@ namespace Avalonia.Headless; /// All UI tests are supposed to be executed from one of the methods to keep execution flow on the UI thread. /// Disposing unit test session stops internal dispatcher loop. /// -public sealed class HeadlessUnitTestSession : IDisposable +public sealed class HeadlessUnitTestSession : IDisposable, IAsyncDisposable { private static readonly Dictionary s_session = new(); @@ -184,6 +184,14 @@ public sealed class HeadlessUnitTestSession : IDisposable _cancellationTokenSource.Dispose(); } + public async ValueTask DisposeAsync() + { + await _cancellationTokenSource.CancelAsync().ConfigureAwait(false); + _queue.CompleteAdding(); + await _dispatchTask.ConfigureAwait(false); + _cancellationTokenSource.Dispose(); + } + /// /// Creates instance of . /// @@ -228,7 +236,9 @@ public sealed class HeadlessUnitTestSession : IDisposable // 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 diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index 88d708335e..a11dea95e4 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -142,7 +142,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IXamlType? itemsCollectionType = null; if (context.GetAvaloniaTypes().BindingBase.IsAssignableFrom(parentItemsValue.Type.GetClrType())) { - if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension) + if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBinding) && parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding) { var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType().FirstOrDefault(); @@ -176,7 +176,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj) { var bindingType = context.GetAvaloniaTypes().BindingBase; - if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension)) + if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && + !(obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension) || + obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))) { return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType()); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 2b7a835fd8..8659eb1299 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -51,6 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ClrPropertyInfo { get; } public IXamlType IPropertyAccessor { get; } public IXamlType PropertyInfoAccessorFactory { get; } + public IXamlType CompiledBinding { get; } public IXamlType CompiledBindingPathBuilder { get; } public IXamlType CompiledBindingPath { get; } public IXamlType CompiledBindingExtension { get; } @@ -242,8 +243,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo"); IPropertyAccessor = cfg.TypeSystem.GetType("Avalonia.Data.Core.Plugins.IPropertyAccessor"); PropertyInfoAccessorFactory = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.PropertyInfoAccessorFactory"); - CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder"); - CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath"); + CompiledBinding = cfg.TypeSystem.GetType("Avalonia.Data.CompiledBinding"); + CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Data.CompiledBindingPathBuilder"); + CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Data.CompiledBindingPath"); CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension"); ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension"); DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate"); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 68dd595ea2..96af9a3a18 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -1013,11 +1013,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var intType = context.Configuration.TypeSystem.GetType("System.Int32"); var types = context.GetAvaloniaTypes(); - // We're calling the CompiledBindingPathBuilder(int apiVersion) with an apiVersion - // of 1 to indicate that we don't want TemplatedParent compatibility hacks enabled. - codeGen - .Ldc_I4(1) - .Newobj(types.CompiledBindingPathBuilder.GetConstructor(new() { intType })); + codeGen.Newobj(types.CompiledBindingPathBuilder.GetConstructor()); foreach (var transform in _transformElements) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 7a52f94a9f..e6186bbea6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -20,7 +20,6 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 0913903ff3..22606bdd93 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -7,15 +7,13 @@ using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.Parsers; -using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; namespace Avalonia.Markup.Xaml.MarkupExtensions { - public sealed class CompiledBindingExtension : BindingBase + public sealed class CompiledBindingExtension : CompiledBinding { public CompiledBindingExtension() { - Path = new CompiledBindingPath(); } public CompiledBindingExtension(CompiledBindingPath path) @@ -23,9 +21,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Path = path; } - public CompiledBindingExtension ProvideValue(IServiceProvider provider) + public CompiledBinding ProvideValue(IServiceProvider? provider) { - return new CompiledBindingExtension + return new CompiledBinding { Path = Path, Delay = Delay, @@ -38,184 +36,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Priority = Priority, StringFormat = StringFormat, Source = Source, - DefaultAnchor = new WeakReference(provider.GetDefaultAnchor()), + DefaultAnchor = new WeakReference(provider?.GetDefaultAnchor()), UpdateSourceTrigger = UpdateSourceTrigger, }; } - /// - /// Gets or sets the amount of time, in milliseconds, to wait before updating the binding - /// source after the value on the target changes. - /// - /// - /// There is no delay when the source is updated via - /// or . Nor is there a delay when - /// is active and a new source object is provided. - /// - public int Delay { get; set; } - - /// - /// Gets or sets the to use. - /// - public IValueConverter? Converter { get; set; } - - /// - /// Gets or sets the culture in which to evaluate the converter. - /// - /// The default value is null. - /// - /// If this property is not set then will be used. - /// - [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] - public CultureInfo? ConverterCulture { get; set; } - - /// - /// Gets or sets a parameter to pass to . - /// - public object? ConverterParameter { get; set; } - public Type? DataType { get; set; } - - /// - /// Gets or sets the value to use when the binding is unable to produce a value. - /// - public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue; - - /// - /// Gets or sets the binding mode. - /// - public BindingMode Mode { get; set; } - - [ConstructorArgument("path")] - public CompiledBindingPath Path { get; set; } - - /// - /// Gets or sets the binding priority. - /// - public BindingPriority Priority { get; set; } - - /// - /// Gets or sets the source for the binding. - /// - public object? Source { get; set; } = AvaloniaProperty.UnsetValue; - - /// - /// Gets or sets the string format. - /// - public string? StringFormat { get; set; } - - /// - /// Gets or sets the value to use when the binding result is null. - /// - public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue; - - /// - /// Gets or sets a value that determines the timing of binding source updates for - /// and bindings. - /// - public UpdateSourceTrigger UpdateSourceTrigger { get; set; } - - internal WeakReference? DefaultAnchor { get; set; } - - internal override BindingExpressionBase CreateInstance( - AvaloniaObject target, - AvaloniaProperty? targetProperty, - object? anchor) - { - var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false; - return InstanceCore(target, targetProperty, anchor, enableDataValidation); - } - - /// - /// Hack for TreeDataTemplate to create a binding expression for an item. - /// - /// The item. - /// - /// Ideally we'd do this in a more generic way but didn't have time to refactor - /// ITreeDataTemplate in time for 11.0. We should revisit this in 12.0. - /// - // TODO12: Refactor - internal BindingExpression CreateObservableForTreeDataTemplate(object source) - { - if (Source != AvaloniaProperty.UnsetValue) - throw new NotSupportedException("Source bindings are not supported in this context."); - - var nodes = new List(); - - Path.BuildExpression(nodes, out var isRooted); - - if (isRooted) - throw new NotSupportedException("Rooted binding paths are not supported in this context."); - - return new BindingExpression( - source, - nodes, - FallbackValue, - delay: TimeSpan.FromMilliseconds(Delay), - converter: Converter, - converterParameter: ConverterParameter, - targetNullValue: TargetNullValue); - } - - private BindingExpression InstanceCore( - AvaloniaObject target, - AvaloniaProperty? targetProperty, - object? anchor, - bool enableDataValidation) - { - var nodes = new List(); - - // Build the expression nodes from the binding path. - Path.BuildExpression(nodes, out var isRooted); - - // If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self, - // #elementName etc.) then we need to add a data context source node. - if (Source == AvaloniaProperty.UnsetValue && !isRooted) - nodes.Insert(0, ExpressionNodeFactory.CreateDataContext(targetProperty)); - - // If the first node is an ISourceNode then allow it to select the source; otherwise - // use the binding source if specified, falling back to the target. - var source = nodes.Count > 0 && nodes[0] is SourceNode sn - ? sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) - : Source != AvaloniaProperty.UnsetValue ? Source : target; - - var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty); - - return new BindingExpression( - source, - nodes, - FallbackValue, - delay: TimeSpan.FromMilliseconds(Delay), - converter: Converter, - converterCulture: ConverterCulture, - converterParameter: ConverterParameter, - enableDataValidation: enableDataValidation, - mode: mode, - priority: Priority, - stringFormat: StringFormat, - targetNullValue: TargetNullValue, - targetProperty: targetProperty, - targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), - updateSourceTrigger: trigger); - } - - private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata( - AvaloniaObject target, - AvaloniaProperty? targetProperty) - { - var mode = Mode; - var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ? - UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger; - - if (mode == BindingMode.Default) - { - if (targetProperty?.GetMetadata(target) is { } metadata) - mode = metadata.DefaultBindingMode; - else - mode = BindingMode.OneWay; - } - - return (mode, trigger); - } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs index da4d7374d4..513b18c7a7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs @@ -33,7 +33,7 @@ namespace Avalonia.Markup.Xaml Type Resolve (string qualifiedTypeName); } - + // TODO12: Move to Avalonia.Base [AttributeUsage(AttributeTargets.Property)] public sealed class ConstructorArgumentAttribute : Attribute { diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj index 9c4c4f62fb..d5f541a014 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj @@ -4,8 +4,6 @@ true true true - - $(WarningsAsErrors);CS0618 diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs index 7621e8436c..b661ad467d 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs @@ -23,7 +23,7 @@ partial class DrawingContextImpl public void PopEffect() { CheckLease(); - Canvas.Restore(); + RestoreCanvas(); } SKImageFilter? CreateEffect(IEffect effect) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index e122c21242..42448ed631 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -27,6 +27,7 @@ namespace Avalonia.Skia private readonly Stack<(SKMatrix matrix, PaintWrapper paint)> _maskStack = new(); private readonly Stack _opacityStack = new(); private readonly Stack _renderOptionsStack = new(); + private readonly Stack _textOptionsStack = new(); private readonly Matrix? _postTransform; private double _currentOpacity = 1.0f; private readonly bool _disableSubpixelTextRendering; @@ -223,6 +224,7 @@ namespace Avalonia.Skia public SKSurface? Surface { get; } public RenderOptions RenderOptions { get; set; } + public TextOptions TextOptions { get; set; } private void CheckLease() { @@ -604,23 +606,32 @@ namespace Avalonia.Skia { var glyphRunImpl = (GlyphRunImpl)glyphRun; - var textRenderOptions = RenderOptions; + // Determine effective TextOptions for text rendering. Start with current pushed TextOptions. + var effectiveTextOptions = TextOptions; + // If subpixel rendering is disabled globally, map subpixel modes to grayscale. if (_disableSubpixelTextRendering) { - switch (textRenderOptions.TextRenderingMode) + var mode = effectiveTextOptions.TextRenderingMode; + + if (mode == TextRenderingMode.SubpixelAntialias || + (mode == TextRenderingMode.Unspecified && (RenderOptions.EdgeMode == EdgeMode.Antialias || RenderOptions.EdgeMode == EdgeMode.Unspecified))) { - case TextRenderingMode.Unspecified - when textRenderOptions.EdgeMode == EdgeMode.Antialias || textRenderOptions.EdgeMode == EdgeMode.Unspecified: - case TextRenderingMode.SubpixelAntialias: - { - textRenderOptions = textRenderOptions with { TextRenderingMode = TextRenderingMode.Antialias }; - break; - } + effectiveTextOptions = effectiveTextOptions with { TextRenderingMode = TextRenderingMode.Antialias }; } } - var textBlob = glyphRunImpl.GetTextBlob(textRenderOptions); + var renderOptions = RenderOptions; + + // If TextRenderingMode is unspecified in TextOptions, use the one from RenderOptions. +#pragma warning disable CS0618 + if (effectiveTextOptions.TextRenderingMode == TextRenderingMode.Unspecified && renderOptions.TextRenderingMode != TextRenderingMode.Unspecified) + { + effectiveTextOptions = effectiveTextOptions with { TextRenderingMode = renderOptions.TextRenderingMode }; + } +#pragma warning restore CS0618 + + var textBlob = glyphRunImpl.GetTextBlob(effectiveTextOptions, RenderOptions); Canvas.DrawText(textBlob, (float)glyphRun.BaselineOrigin.X, (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint); @@ -755,11 +766,25 @@ namespace Avalonia.Skia RenderOptions = RenderOptions.MergeWith(renderOptions); } + public void PushTextOptions(TextOptions textOptions) + { + CheckLease(); + + _textOptionsStack.Push(TextOptions); + + TextOptions = TextOptions.MergeWith(textOptions); + } + public void PopRenderOptions() { RenderOptions = _renderOptionsStack.Pop(); } + public void PopTextOptions() + { + TextOptions = _textOptionsStack.Pop(); + } + /// public virtual void Dispose() { diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs index 30f6da1dc2..e7f772190d 100644 --- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs @@ -15,13 +15,9 @@ namespace Avalonia.Skia private readonly ushort[] _glyphIndices; private readonly SKPoint[] _glyphPositions; - // We use an array as opposed to a ConcurrentDictionary to prevent a large amount of lock object allocations. - // This is possible because the SKFontEdging enum has consecutive integer elements 0, 1, 2, etc. and thus - // can be mapped directly to array indices. - // - // Should Skia update the enum with more elements, then the size of this array should be updated appropriately. - private const int FontEdgingsCount = (int)SKFontEdging.SubpixelAntialias + 1; - private readonly SKTextBlob?[] _textBlobCache = new SKTextBlob?[FontEdgingsCount]; + // A two level cache optimized for single-entry read. Uses TextOptions as a key. + private readonly TwoLevelCache _textBlobCache = + new TwoLevelCache(secondarySize: 3, evictionAction: b => b?.Dispose()); public GlyphRunImpl(GlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos, Point baselineOrigin) @@ -62,7 +58,14 @@ namespace Avalonia.Skia // But the bounds depends on the edging: for now, always use SubpixelAntialias so we have consistent values. // The resulting bounds may be shifted by 1px on some fonts: // "F" text with Inter size 14 has a 0px left bound with SubpixelAntialias but 1px with Antialias. - using var font = CreateFont(SKFontEdging.SubpixelAntialias); + var defaultTextOptions = default(TextOptions) with + { + TextRenderingMode = TextRenderingMode.SubpixelAntialias, + TextHintingMode = TextHintingMode.Strong, + BaselinePixelAlignment = BaselinePixelAlignment.Unaligned + }; + + using var font = CreateFont(defaultTextOptions); var runBounds = new Rect(); var glyphBounds = ArrayPool.Shared.Rent(count); @@ -92,26 +95,19 @@ namespace Avalonia.Skia public Rect Bounds { get; } - public SKTextBlob GetTextBlob(RenderOptions renderOptions) + public SKTextBlob GetTextBlob(TextOptions textOptions, RenderOptions renderOptions) { - var edging = SKFontEdging.SubpixelAntialias; - - switch (renderOptions.TextRenderingMode) + if (textOptions.TextRenderingMode == TextRenderingMode.Unspecified) { - case TextRenderingMode.Alias: - edging = SKFontEdging.Alias; - break; - case TextRenderingMode.Antialias: - edging = SKFontEdging.Antialias; - break; - case TextRenderingMode.Unspecified: - edging = renderOptions.EdgeMode == EdgeMode.Aliased ? SKFontEdging.Alias : SKFontEdging.SubpixelAntialias; - break; + textOptions = textOptions with + { + TextRenderingMode = renderOptions.EdgeMode == EdgeMode.Aliased ? TextRenderingMode.Alias : TextRenderingMode.SubpixelAntialias + }; } - if (_textBlobCache[(int)edging] is null) + return _textBlobCache.GetOrAdd(textOptions, k => { - using var font = CreateFont(edging); + using var font = CreateFont(textOptions); var builder = SKTextBlobBuilderCache.Shared.Get(); @@ -121,37 +117,59 @@ namespace Avalonia.Skia runBuffer.SetGlyphs(_glyphIndices); var textBlob = builder.Build()!; - SKTextBlobBuilderCache.Shared.Return(builder); - - Interlocked.CompareExchange(ref _textBlobCache[(int)edging], textBlob, null); - } - - return _textBlobCache[(int)edging]!; + return textBlob; + }); } - private SKFont CreateFont(SKFontEdging edging) + private SKFont CreateFont(TextOptions textOptions) { + // Determine edging from TextRenderingMode + var edging = textOptions.TextRenderingMode switch + { + TextRenderingMode.Alias => SKFontEdging.Alias, + TextRenderingMode.Antialias => SKFontEdging.Antialias, + TextRenderingMode.SubpixelAntialias => SKFontEdging.SubpixelAntialias, + _ => SKFontEdging.SubpixelAntialias + }; + + // Determine hinting + var hinting = textOptions.TextHintingMode switch + { + TextHintingMode.None => SKFontHinting.None, + TextHintingMode.Light => SKFontHinting.Slight, + TextHintingMode.Strong => SKFontHinting.Full, + _ => SKFontHinting.Full, + }; + + // Force auto-hinting for "Slight" mode (prefer autohinter over bytecode hints), otherwise default. + var forceAutoHinting = textOptions.TextHintingMode == TextHintingMode.Light; + + // Subpixel rendering enabled when edging is not alias. + var subpixel = edging != SKFontEdging.Alias; + + // Baseline snap defaults to true unless explicitly disabled. + var baselineSnap = textOptions.BaselinePixelAlignment != BaselinePixelAlignment.Unaligned; + var font = _glyphTypefaceImpl.CreateSKFont((float)FontRenderingEmSize); - font.Hinting = SKFontHinting.Full; - font.Subpixel = edging != SKFontEdging.Alias; + font.ForceAutoHinting = forceAutoHinting; + font.Hinting = hinting; + font.Subpixel = subpixel; font.Edging = edging; + font.BaselineSnap = baselineSnap; return font; } public void Dispose() { - foreach (var textBlob in _textBlobCache) - { - textBlob?.Dispose(); - } + _textBlobCache.ClearAndDispose(); } public IReadOnlyList GetIntersections(float lowerLimit, float upperLimit) { - var textBlob = GetTextBlob(default); + var textBlob = GetTextBlob(default, default); return textBlob.GetIntercepts(lowerLimit, upperLimit); } diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index 940a1c7247..f56cd274bf 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -1,5 +1,3 @@ -using System.Diagnostics.CodeAnalysis; -using System.Drawing; using Avalonia.Media; using Avalonia.Platform; using SkiaSharp; @@ -78,7 +76,7 @@ namespace Avalonia.Skia /// /// A Skia implementation of a . /// - private class StreamContext : IStreamGeometryContextImpl, IGeometryContext2 + private class StreamContext : IStreamGeometryContextImpl { private readonly StreamGeometryImpl _geometryImpl; private SKPath Stroke => _geometryImpl._strokePath; @@ -122,33 +120,7 @@ namespace Avalonia.Skia } /// - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) - { - var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small; - var sweep = sweepDirection == SweepDirection.Clockwise - ? SKPathDirection.Clockwise - : SKPathDirection.CounterClockwise; - Stroke.ArcTo( - (float)size.Width, - (float)size.Height, - (float)rotationAngle, - arc, - sweep, - (float)point.X, - (float)point.Y); - if (Duplicate) - Fill.ArcTo( - (float)size.Width, - (float)size.Height, - (float)rotationAngle, - arc, - sweep, - (float)point.X, - (float)point.Y); - } - - /// - public void BeginFigure(Point startPoint, bool isFilled) + public void BeginFigure(Point startPoint, bool isFilled = true) { if (!isFilled) EnsureSeparateFillPath(); @@ -161,30 +133,6 @@ namespace Avalonia.Skia Fill.MoveTo((float)startPoint.X, (float)startPoint.Y); } - /// - public void CubicBezierTo(Point point1, Point point2, Point point3) - { - Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y); - if (Duplicate) - Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y); - } - - /// - public void QuadraticBezierTo(Point point1, Point point2) - { - Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y); - if (Duplicate) - Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y); - } - - /// - public void LineTo(Point point) - { - Stroke.LineTo((float)point.X, (float)point.Y); - if (Duplicate) - Fill.LineTo((float)point.X, (float)point.Y); - } - /// public void EndFigure(bool isClosed) { @@ -209,7 +157,7 @@ namespace Avalonia.Skia } /// - public void LineTo(Point point, bool isStroked) + public void LineTo(Point point, bool isStroked = true) { if (isStroked) { @@ -225,7 +173,7 @@ namespace Avalonia.Skia } /// - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked) + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked = true) { var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small; var sweep = sweepDirection == SweepDirection.Clockwise @@ -260,7 +208,7 @@ namespace Avalonia.Skia } /// - public void CubicBezierTo(Point point1, Point point2, Point point3, bool isStroked) + public void CubicBezierTo(Point point1, Point point2, Point point3, bool isStroked = true) { if (isStroked) { @@ -276,7 +224,7 @@ namespace Avalonia.Skia } /// - public void QuadraticBezierTo(Point point1, Point point2, bool isStroked) + public void QuadraticBezierTo(Point point1, Point point2, bool isStroked = true) { if (isStroked) { diff --git a/src/Skia/Avalonia.Skia/TwoLevelCache.cs b/src/Skia/Avalonia.Skia/TwoLevelCache.cs new file mode 100644 index 0000000000..cfc14fae4c --- /dev/null +++ b/src/Skia/Avalonia.Skia/TwoLevelCache.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Skia +{ + /// + /// Provides a lightweight two-level cache for storing key-value pairs, supporting fast retrieval and optional + /// eviction handling. + /// + /// The cache maintains a primary entry for the most recently added item and a secondary array + /// for additional items, with a configurable capacity. When the cache exceeds its capacity, evicted values can be + /// processed using an optional eviction action. This class is intended for internal use and is not + /// thread-safe. + /// The type of keys used to identify cached values. Must be non-nullable. + /// The type of values to be stored in the cache. Must be a reference type. + internal class TwoLevelCache + where TKey : notnull + where TValue : class + { + private readonly int _secondarySize; + private TKey? _primaryKey; + private TValue? _primaryValue; + private KeyValuePair[]? _secondary; + private int _secondaryCount; + private readonly Action? _evictionAction; + private readonly IEqualityComparer _comparer; + + public TwoLevelCache(int secondarySize = 3, Action? evictionAction = null, IEqualityComparer? comparer = null) + { + if (secondarySize < 0) + { + throw new ArgumentOutOfRangeException(nameof(secondarySize)); + } + + _secondarySize = secondarySize; + _evictionAction = evictionAction; + _comparer = comparer ?? EqualityComparer.Default; + } + + public bool TryGet(TKey key, out TValue? value) + { + if (_primaryValue != null && _comparer.Equals(_primaryKey!, key)) + { + value = _primaryValue; + return true; + } + + var sec = _secondary; + if (sec != null) + { + for (int i = 0; i < _secondaryCount; i++) + { + if (_comparer.Equals(sec[i].Key, key)) + { + value = sec[i].Value; + return true; + } + } + } + + value = null; + return false; + } + + public TValue GetOrAdd(TKey key, Func factory) + { + // Check if key already exists + if (TryGet(key, out var existing) && existing != null) + { + return existing; + } + + // Key doesn't exist, create new value + var value = factory(key); + + // Primary is empty - store in primary + if (_primaryValue == null) + { + _primaryKey = key; + _primaryValue = value; + return value; + } + + // No secondary cache configured - replace primary + if (_secondarySize == 0) + { + _evictionAction?.Invoke(_primaryValue); + _primaryKey = key; + _primaryValue = value; + return value; + } + + // Secondary not yet initialized - create it + if (_secondary == null) + { + _secondary = new KeyValuePair[_secondarySize]; + _secondaryCount = 0; + } + + // Shift existing entries right and insert new one at front + // This maintains insertion order and evicts the oldest (last) entry when full + TValue? evicted = default; + bool shouldEvict = _secondaryCount == _secondarySize; + + if (shouldEvict) + { + // Cache is full, last entry will be evicted + evicted = _secondary[_secondarySize - 1].Value; + } + + // Shift existing entries to make room at index 0 + int shiftCount = shouldEvict ? _secondarySize - 1 : _secondaryCount; + for (int i = shiftCount; i > 0; i--) + { + _secondary[i] = _secondary[i - 1]; + } + + // Insert new entry at front + _secondary[0] = new KeyValuePair(key, value); + + // Update count (capped at size) + if (_secondaryCount < _secondarySize) + { + _secondaryCount++; + } + + // Invoke eviction action if we evicted an entry + if (shouldEvict) + { + _evictionAction?.Invoke(evicted); + } + + return value; + } + + public void ClearAndDispose() + { + if (_primaryValue != null) + { + _evictionAction?.Invoke(_primaryValue); + _primaryValue = null; + _primaryKey = default; + } + + if (_secondary != null) + { + for (int i = 0; i < _secondaryCount; i++) + { + _evictionAction?.Invoke(_secondary[i].Value); + } + + _secondary = null; + _secondaryCount = 0; + } + } + } +} diff --git a/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs index 45216a4c09..8562807452 100644 --- a/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs @@ -54,10 +54,8 @@ namespace Avalonia.Win32.Automation { SelectionPatternIdentifiers.IsSelectionRequiredProperty, UiaPropertyId.SelectionIsSelectionRequired }, { SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection }, { SelectionItemPatternIdentifiers.IsSelectedProperty, UiaPropertyId.SelectionItemIsSelected }, - { - SelectionItemPatternIdentifiers.SelectionContainerProperty, - UiaPropertyId.SelectionItemSelectionContainer - } + { SelectionItemPatternIdentifiers.SelectionContainerProperty, UiaPropertyId.SelectionItemSelectionContainer }, + { TogglePatternIdentifiers.ToggleStateProperty, UiaPropertyId.ToggleToggleState }, }; private static ConditionalWeakTable s_nodes = new(); @@ -143,6 +141,7 @@ namespace Avalonia.Win32.Automation UiaPropertyId.LandmarkType => InvokeSync(() => ToUiaLandmarkType(Peer.GetLandmarkType())), UiaPropertyId.LocalizedLandmarkType => InvokeSync(() => ToUiaLocalizedLandmarkType(Peer.GetLandmarkType())), UiaPropertyId.HeadingLevel => InvokeSync(() => ToUiaHeadingLevel(Peer.GetHeadingLevel())), + UiaPropertyId.LiveSetting => InvokeSync(() => ToUiaLiveSetting(Peer.GetLiveSetting())), UiaPropertyId.ProcessId => s_pid, UiaPropertyId.RuntimeId => _runtimeId, _ => null, @@ -275,6 +274,13 @@ namespace Avalonia.Win32.Automation (int)UiaEventId.AutomationFocusChanged); } + protected void RaiseLiveRegionChanged() + { + UiaCoreProviderApi.UiaRaiseAutomationEvent( + this, + (int)UiaEventId.LiveRegionChanged); + } + private RootAutomationNode? GetRoot() { Dispatcher.UIThread.VerifyAccess(); @@ -296,6 +302,11 @@ namespace Avalonia.Win32.Automation e.OldValue as IConvertible, e.NewValue as IConvertible); } + + if (id == UiaPropertyId.Name && Peer.GetLiveSetting() != AutomationLiveSetting.Off) + { + RaiseLiveRegionChanged(); + } } private void OnEmbeddedRootFocusChanged(object? sender, EventArgs e) @@ -359,6 +370,7 @@ namespace Avalonia.Win32.Automation AutomationControlType.Table => UiaControlTypeId.Table, AutomationControlType.TitleBar => UiaControlTypeId.TitleBar, AutomationControlType.Separator => UiaControlTypeId.Separator, + AutomationControlType.Expander => UiaControlTypeId.Group, _ => UiaControlTypeId.Custom, }; } @@ -408,6 +420,8 @@ namespace Avalonia.Win32.Automation }; } + private static UiaLiveSetting ToUiaLiveSetting(AutomationLiveSetting liveSetting) => (UiaLiveSetting)liveSetting; + private static int GetProcessId() { #if NET6_0_OR_GREATER diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs index bbbb2a29e2..bf70aa1f40 100644 --- a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs +++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs @@ -300,6 +300,13 @@ internal enum UiaHeadingLevel Level9 }; +internal enum UiaLiveSetting +{ + Off = 0, + Polite, + Assertive, +}; + #if NET8_0_OR_GREATER [GeneratedComInterface] #else diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index c734c474a6..109cba5850 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,8 +3,6 @@ $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks) true true - - true diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs index e7007c3448..c1e77aab76 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs @@ -1,13 +1,20 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.OpenGL.Egl; using Avalonia.Rendering; -using static Avalonia.Win32.Interop.UnmanagedMethods; -using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods; + using MicroCom.Runtime; +using Windows.Win32; +using Windows.Win32.Graphics.Gdi; + +using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods; +using static Avalonia.Win32.Interop.UnmanagedMethods; + namespace Avalonia.Win32.DirectX { internal unsafe class DxgiConnection : IRenderTimer, IWindowsSurfaceFactory @@ -111,6 +118,8 @@ namespace Avalonia.Win32.DirectX ushort adapterIndex = 0; + Dictionary monitorFrequencies = GetAllMonitorFrequencies(); + // this looks odd, but that's just how one enumerates adapters in DXGI while (fact.EnumAdapters(adapterIndex, &adapterPointer) == 0) { @@ -122,8 +131,12 @@ namespace Avalonia.Win32.DirectX using var output = MicroComRuntime.CreateProxyFor(outputPointer, true); DXGI_OUTPUT_DESC outputDesc = output.Desc; - var screen = Win32Platform.Instance.Screen.ScreenFromHMonitor((IntPtr)outputDesc.Monitor.Value); - var frequency = screen?.Frequency ?? highestRefreshRate; + var hMonitor = new HMONITOR(outputDesc.Monitor.Value); + + var frequency = + monitorFrequencies.TryGetValue(hMonitor, out uint frequencyValue) ? + frequencyValue : + highestRefreshRate; if (highestRefreshRate < frequency) { @@ -145,6 +158,33 @@ namespace Avalonia.Win32.DirectX } + private unsafe Dictionary GetAllMonitorFrequencies() + { + var monitorHandlers = ScreenImpl.GetAllDisplayMonitorHandlers(); + var dictionary = new Dictionary(monitorHandlers.Count); + + foreach (var monitorHandler in monitorHandlers) + { + var info = MONITORINFOEX.Create(); + var hMonitor = new HMONITOR(monitorHandler); + PInvoke.GetMonitorInfo(hMonitor, (MONITORINFO*)&info); + + var deviceMode = new DEVMODEW + { + dmFields = DEVMODE_FIELD_FLAGS.DM_DISPLAYORIENTATION | DEVMODE_FIELD_FLAGS.DM_DISPLAYFREQUENCY, + dmSize = (ushort)Marshal.SizeOf() + }; + PInvoke.EnumDisplaySettings(info.szDevice.ToString(), ENUM_DISPLAY_SETTINGS_MODE.ENUM_CURRENT_SETTINGS, + ref deviceMode); + + var frequency = deviceMode.dmDisplayFrequency; + + dictionary[hMonitor] = frequency; + } + + return dictionary; + } + // Used the windows composition as a blueprint for this startup/creation private static bool TryCreateAndRegisterCore() { @@ -168,6 +208,7 @@ namespace Avalonia.Win32.DirectX }); thread.IsBackground = true; thread.SetApartmentState(System.Threading.ApartmentState.STA); + thread.Name = "DxgiRenderTimerLoop"; thread.Start(); // block until return tcs.Task.Result; diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs index c7306ce86e..6470aa48b8 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs @@ -23,7 +23,8 @@ namespace Avalonia.Win32.DirectX private IUnknown? _renderTexture; private RECT _clientRect; - + private EglSurface? _surface; + public DxgiRenderTarget(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window, EglContext context, DxgiConnection connection) : base(context) { _window = window; @@ -83,7 +84,6 @@ namespace Avalonia.Win32.DirectX } var contextLock = Context.EnsureCurrent(); - EglSurface? surface = null; IDisposable? transaction = null; var success = false; try @@ -96,6 +96,9 @@ namespace Avalonia.Win32.DirectX if (_renderTexture is not null) { + _surface?.Dispose(); + _surface = null; + _renderTexture.Dispose(); _renderTexture = null; } @@ -114,19 +117,24 @@ namespace Avalonia.Win32.DirectX var texture = _renderTexture; if (texture is null) { + _surface?.Dispose(); + _surface = null; + Guid textureGuid = ID3D11Texture2DGuid; texture = MicroComRuntime.CreateProxyFor(_swapChain.GetBuffer(0, &textureGuid), true); } _renderTexture = texture; - // I also have to get the pointer to this texture directly - surface = ((AngleWin32EglDisplay)Context.Display).WrapDirect3D11Texture(MicroComRuntime.GetNativeIntPtr(_renderTexture), - 0, 0, size.Width, size.Height); + if (_surface is null) + { + // I also have to get the pointer to this texture directly + _surface = ((AngleWin32EglDisplay)Context.Display).WrapDirect3D11Texture(MicroComRuntime.GetNativeIntPtr(_renderTexture), + 0, 0, size.Width, size.Height); + } - var res = base.BeginDraw(surface, _window.Size, _window.Scaling, () => + var res = base.BeginDraw(_surface, _window.Size, _window.Scaling, () => { _swapChain.Present((ushort)0U, (ushort)0U); - surface.Dispose(); transaction?.Dispose(); contextLock?.Dispose(); }, true); @@ -137,7 +145,8 @@ namespace Avalonia.Win32.DirectX { if (!success) { - surface?.Dispose(); + _surface?.Dispose(); + _surface = null; if (_renderTexture is not null) { _renderTexture.Dispose(); @@ -155,6 +164,7 @@ namespace Avalonia.Win32.DirectX _dxgiDevice?.Dispose(); _dxgiFactory?.Dispose(); _swapChain?.Dispose(); + _surface?.Dispose(); _renderTexture?.Dispose(); } diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 0f73d39249..b4bff3da35 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -15,6 +15,11 @@ internal unsafe class ScreenImpl : ScreensBase protected override int GetScreenCount() => GetSystemMetrics(SystemMetric.SM_CMONITORS); protected override IReadOnlyList GetAllScreenKeys() + { + return GetAllDisplayMonitorHandlers(); + } + + public static List GetAllDisplayMonitorHandlers() { var screens = new List(); var gcHandle = GCHandle.Alloc(screens); diff --git a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs index 2618dad857..70e35e3973 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Versioning; using Foundation; using Avalonia.Controls.ApplicationLifetimes; using UIKit; @@ -27,6 +28,7 @@ namespace Avalonia.iOS add { _onActivated += value; } remove { _onActivated -= value; } } + event EventHandler IAvaloniaAppDelegate.Deactivated { add { _onDeactivated += value; } @@ -39,6 +41,17 @@ namespace Avalonia.iOS [Export("window")] public UIWindow? Window { get; set; } + [Export("application:configurationForConnectingSceneSession:options:")] + [SupportedOSPlatform("ios13.0")] + [SupportedOSPlatform("tvos13.0")] + [SupportedOSPlatform("maccatalyst")] + public UISceneConfiguration GetConfiguration(UIApplication application, UISceneSession connectingSceneSession, UISceneConnectionOptions options) + { + var config = new UISceneConfiguration(null, connectingSceneSession.Role); + config.DelegateType = typeof(AvaloniaSceneDelegate); + return config; + } + [Export("application:didFinishLaunchingWithOptions:")] public bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions) { @@ -46,28 +59,27 @@ namespace Avalonia.iOS builder = CustomizeAppBuilder(builder); var lifetime = new SingleViewLifetime(); - - builder.AfterApplicationSetup(_ => - { - Window = new UIWindow(); - - var view = new AvaloniaView(); - lifetime.View = view; - var controller = new DefaultAvaloniaViewController - { - View = view - }; - Window.RootViewController = controller; - view.InitWithController(controller); - }); - + builder.AfterApplicationSetup(_ => CreateAndInitWindow(lifetime)); builder.SetupWithLifetime(lifetime); - Window!.MakeKeyAndVisible(); + Window?.MakeKeyAndVisible(); return true; } + private void CreateAndInitWindow(SingleViewLifetime lifetime) + { + if (OperatingSystem.IsIOSVersionAtLeast(13) || + OperatingSystem.IsTvOSVersionAtLeast(13) || + OperatingSystem.IsMacCatalyst()) + { + return; + } + + Window = new UIWindow(); + AvaloniaSceneDelegate.InitWindow(Window, lifetime); + } + [Export("application:openURL:options:")] public bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { diff --git a/src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs new file mode 100644 index 0000000000..e4bbb4843e --- /dev/null +++ b/src/iOS/Avalonia.iOS/AvaloniaSceneDelegate.cs @@ -0,0 +1,36 @@ +using Foundation; +using UIKit; + +namespace Avalonia.iOS; + +internal sealed class AvaloniaSceneDelegate : UIResponder, IUIWindowSceneDelegate +{ + [Export("window")] + public UIWindow? Window { get; set; } + + [Export("scene:willConnectToSession:options:")] + public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions) + { + if (session.Configuration.Name is not null || + scene is not UIWindowScene windowScene || + Application.Current?.ApplicationLifetime is not SingleViewLifetime lifetime) + { + return; + } + + Window = new UIWindow(windowScene); + InitWindow(Window, lifetime); + + Window.MakeKeyAndVisible(); + } + + internal static void InitWindow(UIWindow window, SingleViewLifetime lifetime) + { + var view = new AvaloniaView(); + lifetime.View = view; + + var controller = new DefaultAvaloniaViewController { View = view }; + window.RootViewController = controller; + view.InitWithController(controller); + } +} diff --git a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs index 7c56904fdc..913148197d 100644 --- a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs +++ b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.iOS; @@ -10,9 +8,9 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLe private Control? _mainView; private AvaloniaView? _view; - public AvaloniaView View + public AvaloniaView? View { - [return: MaybeNull] get => _view!; + get => _view; internal set { if (_view != null) @@ -21,7 +19,7 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLe _view.Dispose(); } _view = value; - _view.Content = _mainView; + _view?.Content = _mainView; } } @@ -33,10 +31,7 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLe if (_mainView != value) { _mainView = value; - if (_view != null) - { - _view.Content = _mainView; - } + _view?.Content = _mainView; } } } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs b/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs index 0662fd94b0..d3623fde80 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; +using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs index 02c67c5896..a12e5015fd 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs @@ -54,9 +54,9 @@ public abstract class PointerTestsBase : ScopedTestBase impl.Setup(r => r.PointToScreen(It.IsAny())).Returns(p => new PixelPoint((int)p.X, (int)p.Y)); impl.Setup(r => r.PointToClient(It.IsAny())).Returns(p => new Point(p.X, p.Y)); - var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screen1 = new MockScreen(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); var screens = new Mock(); - screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1); impl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); return impl; diff --git a/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs deleted file mode 100644 index b7d09a40d7..0000000000 --- a/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs +++ /dev/null @@ -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(() => 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(() => 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())); - } - } -} diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index dcfd7d8ab2..51ed2296a3 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -666,7 +666,7 @@ namespace Avalonia.Controls.UnitTests var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); var screenImpl = new Mock(); screenImpl.Setup(x => x.ScreenCount).Returns(1); - screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) }); + screenImpl.Setup(X => X.AllScreens).Returns( new[] { new MockScreen(1, screen, screen, true) }); var windowImpl = MockWindowingPlatform.CreateWindowMock(); _popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index bae1f91319..aa213d996c 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -300,9 +300,9 @@ namespace Avalonia.Controls.UnitTests windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); - var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screen1 = new MockScreen(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); var screens = new Mock(); - screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1); windowImpl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); var services = TestServices.StyledWindow.With( diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index 35bf427bc1..c143f8db12 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -848,7 +848,7 @@ namespace Avalonia.Controls.UnitTests var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); var screenImpl = new Mock(); screenImpl.Setup(x => x.ScreenCount).Returns(1); - screenImpl.Setup(X => X.AllScreens).Returns(new[] { new Screen(1, screen, screen, true) }); + screenImpl.Setup(X => X.AllScreens).Returns(new[] { new MockScreen(1, screen, screen, true) }); var windowImpl = MockWindowingPlatform.CreateWindowMock(); _popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object); diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/ShapeTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/ShapeTests.cs index ac5dded8fc..b7b5604135 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/ShapeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/ShapeTests.cs @@ -180,6 +180,10 @@ public class ShapeTests : ScopedTestBase protected override void PushRenderOptionsCore(RenderOptions renderOptions) { + } + + protected override void PushTextOptionsCore(TextOptions textOptions) + { } protected override void PushTransformCore(Matrix matrix) @@ -210,6 +214,10 @@ public class ShapeTests : ScopedTestBase { } + protected override void PopTextOptionsCore() + { + } + protected override void DisposeCore() { } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 2ec0b7f80d..24b17c46f7 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -518,11 +518,11 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Window_Should_Not_Be_Centered_When_WindowStartupLocation_Is_CenterScreen_And_Window_Is_Hidden_And_Shown() { - var screen1 = new Mock(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + var screen1 = new MockScreen(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); var screens = new Mock(); - screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object }); - screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1 }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); @@ -553,12 +553,12 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { - var screen1 = new Mock(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); - var screen2 = new Mock(1.0, new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false); + var screen1 = new MockScreen(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + var screen2 = new MockScreen(1.0, new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false); var screens = new Mock(); - screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); - screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1, screen2 }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); @@ -576,8 +576,8 @@ namespace Avalonia.Controls.UnitTests window.Show(); var expectedPosition = new PixelPoint( - (int)(screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2), - (int)(screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2)); + (int)(screen1.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2), + (int)(screen1.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2)); Assert.Equal(window.Position, expectedPosition); } @@ -586,10 +586,10 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Window_Should_Be_Sized_To_MinSize_If_InitialSize_Less_Than_MinSize() { - var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screen1 = new MockScreen(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); var screens = new Mock(); - screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object }); - screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1 }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1); var windowImpl = MockWindowingPlatform.CreateWindowMock(400, 300); windowImpl.Setup(x => x.DesktopScaling).Returns(1.75); @@ -1146,9 +1146,9 @@ namespace Avalonia.Controls.UnitTests private static Mock CreateImpl() { - var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screen1 = new MockScreen(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); var screens = new Mock(); - screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1); var windowImpl = new Mock(); windowImpl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor()); diff --git a/tests/Avalonia.Headless.XUnit.PerAssembly.UnitTests/Avalonia.Headless.XUnit.PerAssembly.UnitTests.csproj b/tests/Avalonia.Headless.XUnit.PerAssembly.UnitTests/Avalonia.Headless.XUnit.PerAssembly.UnitTests.csproj index e638032870..270e078ed3 100644 --- a/tests/Avalonia.Headless.XUnit.PerAssembly.UnitTests/Avalonia.Headless.XUnit.PerAssembly.UnitTests.csproj +++ b/tests/Avalonia.Headless.XUnit.PerAssembly.UnitTests/Avalonia.Headless.XUnit.PerAssembly.UnitTests.csproj @@ -4,7 +4,6 @@ Exe true $(DefineConstants);XUNIT - true diff --git a/tests/Avalonia.Headless.XUnit.PerTest.UnitTests/Avalonia.Headless.XUnit.PerTest.UnitTests.csproj b/tests/Avalonia.Headless.XUnit.PerTest.UnitTests/Avalonia.Headless.XUnit.PerTest.UnitTests.csproj index e638032870..270e078ed3 100644 --- a/tests/Avalonia.Headless.XUnit.PerTest.UnitTests/Avalonia.Headless.XUnit.PerTest.UnitTests.csproj +++ b/tests/Avalonia.Headless.XUnit.PerTest.UnitTests/Avalonia.Headless.XUnit.PerTest.UnitTests.csproj @@ -4,7 +4,6 @@ Exe true $(DefineConstants);XUNIT - true diff --git a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj index a11ecc1fb4..797265af46 100644 --- a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj +++ b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj @@ -13,10 +13,7 @@ - - - - + diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 0ff811ff22..11197888a5 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -402,9 +402,9 @@ namespace Avalonia.LeakTests { using (Start()) { - var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screen1 = new MockScreen(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); var screens = new Mock(); - screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1); var impl = new Mock(); impl.Setup(r => r.TryGetFeature(It.IsAny())).Returns((object?)null); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 051129e12c..727b3ddc45 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -639,8 +639,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions target.ApplyTemplate(); // Assert DataGridLikeColumn.Binding data type. - var compiledPath = ((CompiledBindingExtension)column.Binding!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var compiledPath = ((CompiledBinding)column.Binding!).Path; + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); Assert.Equal(nameof(TestData.StringProperty), node.Property.Name); @@ -682,8 +682,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions target.ApplyTemplate(); // Assert DataGridLikeColumn.Binding data type. - var compiledPath = ((CompiledBindingExtension)column.Binding!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var compiledPath = ((CompiledBinding)column.Binding!).Path; + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(int), node.Property.PropertyType); // Assert DataGridLikeColumn.Template data type by evaluating the template. @@ -727,8 +727,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions target.ApplyTemplate(); // Assert DataGridLikeColumn.Binding data type. - var compiledPath = ((CompiledBindingExtension)column.Binding!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var compiledPath = ((CompiledBinding)column.Binding!).Path; + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(int), node.Property.PropertyType); // Assert DataGridLikeColumn.Template data type by evaluating the template. @@ -2060,9 +2060,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions x:DataType='local:TestDataContext' X='{CompiledBinding StringProperty}' />"; var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var compiledPath = ((CompiledBindingExtension)control.X!).Path; + var compiledPath = ((CompiledBinding)control.X!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); } } @@ -2078,9 +2078,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />"; var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var compiledPath = ((CompiledBindingExtension)control.X!).Path; + var compiledPath = ((CompiledBinding)control.X!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); } } @@ -2097,9 +2097,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />"; var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true }); - var compiledPath = ((CompiledBindingExtension)control.X!).Path; + var compiledPath = ((CompiledBinding)control.X!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); } } diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs index 45b8c5d2ce..deecc9eae3 100644 --- a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -385,6 +385,44 @@ namespace Avalonia.Skia.RenderTests FontFamily = new FontFamily(symbolsFont) }; } + + [InlineData(TextRenderingMode.Antialias, TextHintingMode.None)] + [InlineData(TextRenderingMode.Alias, TextHintingMode.None)] + [InlineData(TextRenderingMode.Antialias, TextHintingMode.Light)] + [InlineData(TextRenderingMode.Alias, TextHintingMode.Light)] + [Win32Theory("Depends on the backend")] + public async Task Should_Render_TextBlock_With_TextOptions( + TextRenderingMode textRenderingMode, + TextHintingMode textHintingMode) + { + var textBlock = new TextBlock + { + FontFamily = TestFontFamily, + FontSize = 24, + Foreground = Brushes.Black, + Text = "TextOptions", + Background = Brushes.LightGray, + Padding = new Thickness(10) + }; + + TextOptions.SetTextOptions(textBlock, new TextOptions + { + TextRenderingMode = textRenderingMode, + TextHintingMode = textHintingMode + }); + + var target = new Border + { + Width = 300, + Height = 100, + Background = Brushes.White, + Child = textBlock + }; + + var testName = $"Should_Render_TextBlock_With_TextOptions_{textRenderingMode}_{textHintingMode}"; + await RenderToFile(target, testName); + CompareImages(testName); + } } } diff --git a/tests/Avalonia.RenderTests/Media/EffectTests.cs b/tests/Avalonia.RenderTests/Media/EffectTests.cs index 9a83b397f4..c8b931dae8 100644 --- a/tests/Avalonia.RenderTests/Media/EffectTests.cs +++ b/tests/Avalonia.RenderTests/Media/EffectTests.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Controls.Shapes; using Avalonia.Media; using Xunit; #pragma warning disable CS0649 @@ -38,6 +39,43 @@ public class EffectTests : TestBase await RenderToFile(target); CompareImages(skipImmediate: true); } + + [Fact] + public async Task EffectFollowedByNonEffect() + { + var target = new Border + { + Background = Brushes.White, + Width = 200, + Height = 200, + Child = new Panel + { + Margin = new Thickness(25), + Children = + { + new Rectangle + { + Fill = Brushes.Yellow, + Effect = new DropShadowEffect + { + Opacity = 1, + OffsetX = 0, + OffsetY = 0, + Color = Colors.Black, + BlurRadius = 50 + } + }, + new Rectangle + { + Fill = new SolidColorBrush(0x7F007FFF) + } + } + } + }; + + await RenderToFile(target); + CompareImages(skipImmediate: true); + } } -#endif \ No newline at end of file +#endif diff --git a/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs b/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs index b28762d08d..ad0cf67d2f 100644 --- a/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs @@ -115,7 +115,7 @@ namespace Avalonia.Skia.RenderTests [TextElement.ForegroundProperty] = new SolidColorBrush { Color = Colors.Black } }; - RenderOptions.SetTextRenderingMode(control, TextRenderingMode.Alias); + TextOptions.SetTextRenderingMode(control, TextRenderingMode.Alias); Decorator target = new Decorator { diff --git a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs index 1882ef9ccb..8f74f0779f 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs @@ -169,7 +169,7 @@ namespace Avalonia.Skia.UnitTests.Media var glyphRun1 = CreateGlyphRun(shapedBuffer); var bounds1 = glyphRun1.InkBounds; - ((GlyphRunImpl)glyphRun1.PlatformImpl.Item).GetTextBlob(new RenderOptions { TextRenderingMode = TextRenderingMode.SubpixelAntialias }); + ((GlyphRunImpl)glyphRun1.PlatformImpl.Item).GetTextBlob(new TextOptions { TextRenderingMode = TextRenderingMode.SubpixelAntialias }, default); var bounds2 = CreateGlyphRun(shapedBuffer).InkBounds; diff --git a/tests/Avalonia.Skia.UnitTests/TwoLevelCacheTests.cs b/tests/Avalonia.Skia.UnitTests/TwoLevelCacheTests.cs new file mode 100644 index 0000000000..6946c7ec2b --- /dev/null +++ b/tests/Avalonia.Skia.UnitTests/TwoLevelCacheTests.cs @@ -0,0 +1,351 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Avalonia.Skia; +using Xunit; + +namespace Avalonia.Skia.UnitTests +{ + public class TwoLevelCacheTests + { + [Fact] + public void Constructor_WithNegativeSecondarySize_ThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => new TwoLevelCache(-1)); + } + + [Fact] + public void Constructor_WithZeroSecondarySize_DoesNotThrow() + { + var cache = new TwoLevelCache(0); + Assert.NotNull(cache); + } + + [Fact] + public void TryGet_EmptyCache_ReturnsFalse() + { + var cache = new TwoLevelCache(); + + var result = cache.TryGet("key", out var value); + + Assert.False(result); + Assert.Null(value); + } + + [Fact] + public void GetOrAdd_FirstItem_StoresInPrimary() + { + var cache = new TwoLevelCache(); + var value = new object(); + + var result = cache.GetOrAdd("key1", _ => value); + + Assert.Same(value, result); + Assert.True(cache.TryGet("key1", out var retrieved)); + Assert.Same(value, retrieved); + } + + [Fact] + public void GetOrAdd_SameKey_ReturnsExistingValue() + { + var cache = new TwoLevelCache(); + var value1 = new object(); + var value2 = new object(); + + cache.GetOrAdd("key", _ => value1); + var result = cache.GetOrAdd("key", _ => value2); + + Assert.Same(value1, result); + } + + [Fact] + public void GetOrAdd_SecondItem_StoresInSecondary() + { + var cache = new TwoLevelCache(secondarySize: 3); + var value1 = new object(); + var value2 = new object(); + + cache.GetOrAdd("key1", _ => value1); + cache.GetOrAdd("key2", _ => value2); + + Assert.True(cache.TryGet("key1", out var retrieved1)); + Assert.Same(value1, retrieved1); + Assert.True(cache.TryGet("key2", out var retrieved2)); + Assert.Same(value2, retrieved2); + } + + [Fact] + public void GetOrAdd_MultipleItems_StoresCorrectly() + { + var cache = new TwoLevelCache(secondarySize: 3); + var values = new object[4]; + for (int i = 0; i < 4; i++) + { + values[i] = new object(); + cache.GetOrAdd($"key{i}", _ => values[i]); + } + + // All should be retrievable + for (int i = 0; i < 4; i++) + { + Assert.True(cache.TryGet($"key{i}", out var retrieved)); + Assert.Same(values[i], retrieved); + } + } + + [Fact] + public void GetOrAdd_ExceedsCapacity_CallsEvictionAction() + { + var evictedValues = new List(); + var cache = new TwoLevelCache( + secondarySize: 2, + evictionAction: v => evictedValues.Add(v)); + + var value1 = new object(); + var value2 = new object(); + var value3 = new object(); + var value4 = new object(); + + cache.GetOrAdd("key1", _ => value1); + cache.GetOrAdd("key2", _ => value2); + cache.GetOrAdd("key3", _ => value3); + + // No evictions yet + Assert.Empty(evictedValues); + + // This should cause eviction + cache.GetOrAdd("key4", _ => value4); + + Assert.Single(evictedValues); + Assert.Same(value2, evictedValues[0]); + } + + [Fact] + public void GetOrAdd_ZeroSecondarySize_EvictsPrimaryImmediately() + { + var evictedValues = new List(); + var cache = new TwoLevelCache( + secondarySize: 0, + evictionAction: v => evictedValues.Add(v)); + + var value1 = new object(); + var value2 = new object(); + + cache.GetOrAdd("key1", _ => value1); + cache.GetOrAdd("key2", _ => value2); + + Assert.Single(evictedValues); + Assert.Same(value1, evictedValues[0]); + + // Only the latest value should be retrievable + Assert.False(cache.TryGet("key1", out _)); + Assert.True(cache.TryGet("key2", out var retrieved)); + Assert.Same(value2, retrieved); + } + + [Fact] + public void GetOrAdd_DuplicateKey_ReturnsExistingWithoutCallingFactory() + { + var cache = new TwoLevelCache(); + var value1 = new object(); + var factoryCalled = false; + + // Add initial value + cache.GetOrAdd("key", _ => value1); + + // Try to add again - factory should not be called + var result = cache.GetOrAdd("key", _ => + { + factoryCalled = true; + return new object(); + }); + + // Should return first value without calling factory + Assert.Same(value1, result); + Assert.False(factoryCalled); + } + + [Fact] + public void GetOrAdd_DuplicateKeyInSecondary_ReturnsExistingWithoutCallingFactory() + { + var cache = new TwoLevelCache(secondarySize: 2); + var value1 = new object(); + var value2 = new object(); + var factoryCalled = false; + + cache.GetOrAdd("key1", _ => value1); + cache.GetOrAdd("key2", _ => value2); + + // Try to add key2 again - factory should not be called + var result = cache.GetOrAdd("key2", _ => + { + factoryCalled = true; + return new object(); + }); + + Assert.Same(value2, result); + Assert.False(factoryCalled); + } + + [Fact] + public void ClearAndDispose_EmptyCache_DoesNotThrow() + { + var cache = new TwoLevelCache(); + cache.ClearAndDispose(); + } + + [Fact] + public void ClearAndDispose_WithValues_CallsEvictionActionForAll() + { + var evictedValues = new List(); + var cache = new TwoLevelCache( + secondarySize: 2, + evictionAction: v => evictedValues.Add(v)); + + var value1 = new object(); + var value2 = new object(); + var value3 = new object(); + + cache.GetOrAdd("key1", _ => value1); + cache.GetOrAdd("key2", _ => value2); + cache.GetOrAdd("key3", _ => value3); + + cache.ClearAndDispose(); + + Assert.Equal(3, evictedValues.Count); + Assert.Contains(value1, evictedValues); + Assert.Contains(value2, evictedValues); + Assert.Contains(value3, evictedValues); + } + + [Fact] + public void ClearAndDispose_ClearsAllEntries() + { + var cache = new TwoLevelCache(secondarySize: 2); + + cache.GetOrAdd("key1", _ => new object()); + cache.GetOrAdd("key2", _ => new object()); + cache.ClearAndDispose(); + + Assert.False(cache.TryGet("key1", out _)); + Assert.False(cache.TryGet("key2", out _)); + } + + [Fact] + public void GetOrAdd_WithCustomComparer_UsesComparer() + { + var comparer = StringComparer.OrdinalIgnoreCase; + var cache = new TwoLevelCache(comparer: comparer); + + var value = new object(); + cache.GetOrAdd("KEY", _ => value); + + Assert.True(cache.TryGet("key", out var retrieved)); + Assert.Same(value, retrieved); + } + + [Fact] + public void TryGet_WithCustomComparer_UsesComparer() + { + var comparer = StringComparer.OrdinalIgnoreCase; + var cache = new TwoLevelCache( + secondarySize: 2, + comparer: comparer); + + var value1 = new object(); + var value2 = new object(); + + cache.GetOrAdd("PRIMARY", _ => value1); + cache.GetOrAdd("SECONDARY", _ => value2); + + Assert.True(cache.TryGet("primary", out var retrieved1)); + Assert.Same(value1, retrieved1); + Assert.True(cache.TryGet("secondary", out var retrieved2)); + Assert.Same(value2, retrieved2); + } + + [Fact] + public void GetOrAdd_IntKeys_WorksCorrectly() + { + var cache = new TwoLevelCache(secondarySize: 2); + + var value1 = new object(); + var value2 = new object(); + var value3 = new object(); + + cache.GetOrAdd(1, _ => value1); + cache.GetOrAdd(2, _ => value2); + cache.GetOrAdd(3, _ => value3); + + Assert.True(cache.TryGet(1, out var retrieved1)); + Assert.Same(value1, retrieved1); + Assert.True(cache.TryGet(2, out var retrieved2)); + Assert.Same(value2, retrieved2); + Assert.True(cache.TryGet(3, out var retrieved3)); + Assert.Same(value3, retrieved3); + } + + [Fact] + public void GetOrAdd_RotatesSecondaryCorrectly() + { + var evictedValues = new List(); + var cache = new TwoLevelCache( + secondarySize: 2, + evictionAction: v => evictedValues.Add(v)); + + var values = new object[5]; + for (int i = 0; i < 5; i++) + { + values[i] = new object(); + cache.GetOrAdd(i, _ => values[i]); + } + + // Primary: 0, Secondary: [1, 2] + // After adding 3: Primary: 0, Secondary: [3, 1] (evicts 2) + // After adding 4: Primary: 0, Secondary: [4, 3] (evicts 1) + + Assert.Equal(2, evictedValues.Count); + Assert.Contains(values[2], evictedValues); + Assert.Contains(values[1], evictedValues); + + // These should still be in cache + Assert.True(cache.TryGet(0, out _)); + Assert.True(cache.TryGet(3, out _)); + Assert.True(cache.TryGet(4, out _)); + + // These should be evicted + Assert.False(cache.TryGet(1, out _)); + Assert.False(cache.TryGet(2, out _)); + } + + [Fact] + public void FactoryFunction_ReceivesCorrectKey() + { + var cache = new TwoLevelCache(); + string? capturedKey = null; + + cache.GetOrAdd("testKey", key => + { + capturedKey = key; + return "value"; + }); + + Assert.Equal("testKey", capturedKey); + } + + [Fact] + public void GetOrAdd_NullEvictionAction_DoesNotThrow() + { + var cache = new TwoLevelCache( + secondarySize: 1, + evictionAction: null); + + cache.GetOrAdd("key1", _ => new object()); + cache.GetOrAdd("key2", _ => new object()); + cache.GetOrAdd("key3", _ => new object()); // Should evict without error + + cache.ClearAndDispose(); // Should also not throw + } + } +} diff --git a/tests/Avalonia.UnitTests/MockScreen.cs b/tests/Avalonia.UnitTests/MockScreen.cs new file mode 100644 index 0000000000..adf6ebf24b --- /dev/null +++ b/tests/Avalonia.UnitTests/MockScreen.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; +using Avalonia.Platform; + +namespace Avalonia.UnitTests; + +internal class MockScreen : Screen +{ + public MockScreen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary) + { + Scaling = scaling; + Bounds = bounds; + WorkingArea = workingArea; + IsPrimary = isPrimary; + } + + public override int GetHashCode() + => RuntimeHelpers.GetHashCode(this); + + public override bool Equals(Screen? other) + => ReferenceEquals(this, other); +} diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 23b96538fe..9ddcb217ec 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -126,7 +126,7 @@ namespace Avalonia.UnitTests { var screenImpl = new Mock(); var bounds = new PixelRect(0, 0, (int)s_screenSize.Width, (int)s_screenSize.Height); - var screen = new Screen(96, bounds, bounds, true); + var screen = new MockScreen(96, bounds, bounds, true); screenImpl.Setup(x => x.AllScreens).Returns(new[] { screen }); screenImpl.Setup(x => x.ScreenCount).Returns(1); return screenImpl; diff --git a/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Alias_Light.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Alias_Light.expected.png new file mode 100644 index 0000000000..0610d12ae7 Binary files /dev/null and b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Alias_Light.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Alias_None.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Alias_None.expected.png new file mode 100644 index 0000000000..26b2d096d1 Binary files /dev/null and b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Alias_None.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Antialias_Light.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Antialias_Light.expected.png new file mode 100644 index 0000000000..d9634a250a Binary files /dev/null and b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Antialias_Light.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Antialias_None.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Antialias_None.expected.png new file mode 100644 index 0000000000..f5b987243f Binary files /dev/null and b/tests/TestFiles/Skia/Controls/TextBlock/Should_Render_TextBlock_With_TextOptions_Antialias_None.expected.png differ diff --git a/tests/TestFiles/Skia/Media/Effects/EffectFollowedByNonEffect.expected.png b/tests/TestFiles/Skia/Media/Effects/EffectFollowedByNonEffect.expected.png new file mode 100644 index 0000000000..e0f57cecf6 Binary files /dev/null and b/tests/TestFiles/Skia/Media/Effects/EffectFollowedByNonEffect.expected.png differ