diff --git a/.editorconfig b/.editorconfig
index 8c6ad717e4..bfeab8c84f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -138,33 +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
-
-# CS1591: Missing XML comment for publicly visible type or member
-dotnet_diagnostic.CS1591.severity = suggestion
-
-# 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
@@ -182,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/.gitmodules b/.gitmodules
index 9c58db0b3a..07f532607a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,6 +4,3 @@
[submodule "XamlX"]
path = external/XamlX
url = https://github.com/kekekeks/XamlX.git
-[submodule "Avalonia.Controls.DataGrid"]
- path = external/Avalonia.Controls.DataGrid
- url = https://github.com/AvaloniaUI/Avalonia.Controls.DataGrid.git
diff --git a/.ncrunch/AppWithoutLifetime.v3.ncrunchproject b/.ncrunch/AppWithoutLifetime.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/AppWithoutLifetime.v3.ncrunchproject
+++ b/.ncrunch/AppWithoutLifetime.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Analyzers.v3.ncrunchproject b/.ncrunch/Avalonia.Analyzers.CSharp.v3.ncrunchproject
similarity index 63%
rename from .ncrunch/Avalonia.Analyzers.v3.ncrunchproject
rename to .ncrunch/Avalonia.Analyzers.CSharp.v3.ncrunchproject
index a079eefacf..08b7dfa6ee 100644
--- a/.ncrunch/Avalonia.Analyzers.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Analyzers.CSharp.v3.ncrunchproject
@@ -3,5 +3,6 @@
TargetFramework = netstandard2.0
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.iOS.v3.ncrunchproject b/.ncrunch/Avalonia.Analyzers.CodeFixes.CSharp.v3.ncrunchproject
similarity index 63%
rename from .ncrunch/Avalonia.iOS.v3.ncrunchproject
rename to .ncrunch/Avalonia.Analyzers.CodeFixes.CSharp.v3.ncrunchproject
index 00c1cea8f4..08b7dfa6ee 100644
--- a/.ncrunch/Avalonia.iOS.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Analyzers.CodeFixes.CSharp.v3.ncrunchproject
@@ -3,6 +3,6 @@
TargetFramework = netstandard2.0
- True
+ False
\ No newline at end of file
diff --git a/.ncrunch/MiniMvvm.v3.ncrunchproject b/.ncrunch/Avalonia.Analyzers.VisualBasic.v3.ncrunchproject
similarity index 63%
rename from .ncrunch/MiniMvvm.v3.ncrunchproject
rename to .ncrunch/Avalonia.Analyzers.VisualBasic.v3.ncrunchproject
index 00c1cea8f4..08b7dfa6ee 100644
--- a/.ncrunch/MiniMvvm.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Analyzers.VisualBasic.v3.ncrunchproject
@@ -3,6 +3,6 @@
TargetFramework = netstandard2.0
- True
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Android.v3.ncrunchproject b/.ncrunch/Avalonia.Android.v3.ncrunchproject
index 1335822600..a739b1286b 100644
--- a/.ncrunch/Avalonia.Android.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Android.v3.ncrunchproject
@@ -1,7 +1,7 @@
- TargetFramework = net8.0-android34.0
+ TargetFramework = net10.0-android36.0
True
diff --git a/.ncrunch/Avalonia.Base.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Base.UnitTests.v3.ncrunchproject
index 1733492c89..0bcc569d05 100644
--- a/.ncrunch/Avalonia.Base.UnitTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Base.UnitTests.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = net8.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Base.v3.ncrunchproject b/.ncrunch/Avalonia.Base.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Base.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject b/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject b/.ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject
deleted file mode 100644
index 7e6955b244..0000000000
--- a/.ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0-browser
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Browser.v3.ncrunchproject b/.ncrunch/Avalonia.Browser.v3.ncrunchproject
deleted file mode 100644
index 7e6955b244..0000000000
--- a/.ncrunch/Avalonia.Browser.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0-browser
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Build.Tasks.UnitTest.v3.ncrunchproject b/.ncrunch/Avalonia.Build.Tasks.UnitTest.v3.ncrunchproject
deleted file mode 100644
index f144353670..0000000000
--- a/.ncrunch/Avalonia.Build.Tasks.UnitTest.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net472
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject b/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject
index a079eefacf..08b7dfa6ee 100644
--- a/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject
@@ -3,5 +3,6 @@
TargetFramework = netstandard2.0
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Controls.DataGrid.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.DataGrid.UnitTests.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.Controls.DataGrid.UnitTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Controls.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.UnitTests.v3.ncrunchproject
index 1733492c89..0bcc569d05 100644
--- a/.ncrunch/Avalonia.Controls.UnitTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Controls.UnitTests.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = net8.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Controls.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Controls.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject b/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.DesignerSupport.TestApp.v3.ncrunchproject b/.ncrunch/Avalonia.DesignerSupport.TestApp.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.DesignerSupport.TestApp.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.DesignerSupport.Tests.v3.ncrunchproject b/.ncrunch/Avalonia.DesignerSupport.Tests.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/Avalonia.DesignerSupport.Tests.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject b/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Desktop.v3.ncrunchproject b/.ncrunch/Avalonia.Desktop.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Desktop.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.AndroidTestApplication.v3.ncrunchproject b/.ncrunch/Avalonia.Diagnostics.net6.0.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.AndroidTestApplication.v3.ncrunchproject
rename to .ncrunch/Avalonia.Diagnostics.net6.0.v3.ncrunchproject
diff --git a/.ncrunch/Avalonia.Desktop.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Diagnostics.net8.0.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.Desktop.net6.0.v3.ncrunchproject
rename to .ncrunch/Avalonia.Diagnostics.net8.0.v3.ncrunchproject
diff --git a/.ncrunch/Avalonia.Desktop.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Diagnostics.netstandard2.0.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.Desktop.netstandard2.0.v3.ncrunchproject
rename to .ncrunch/Avalonia.Diagnostics.netstandard2.0.v3.ncrunchproject
diff --git a/.ncrunch/Avalonia.Dialogs.v3.ncrunchproject b/.ncrunch/Avalonia.Dialogs.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Dialogs.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Fonts.Inter.v3.ncrunchproject b/.ncrunch/Avalonia.Fonts.Inter.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Fonts.Inter.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.FreeDesktop.v3.ncrunchproject b/.ncrunch/Avalonia.FreeDesktop.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.FreeDesktop.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject b/.ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Generators.v3.ncrunchproject b/.ncrunch/Avalonia.Generators.v3.ncrunchproject
index a079eefacf..08b7dfa6ee 100644
--- a/.ncrunch/Avalonia.Generators.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Generators.v3.ncrunchproject
@@ -3,5 +3,6 @@
TargetFramework = netstandard2.0
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.HarfBuzz.v3.ncrunchproject b/.ncrunch/Avalonia.HarfBuzz.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.HarfBuzz.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.NUnit.PerAssembly.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.NUnit.PerAssembly.UnitTests.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.NUnit.PerAssembly.UnitTests.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.NUnit.PerTest.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.NUnit.PerTest.UnitTests.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.NUnit.PerTest.UnitTests.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.NUnit.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.NUnit.UnitTests.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.Headless.NUnit.UnitTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.NUnit.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.NUnit.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.NUnit.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.Vnc.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.Vnc.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.Vnc.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.XUnit.PerAssembly.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.XUnit.PerAssembly.UnitTests.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.XUnit.PerAssembly.UnitTests.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.XUnit.PerTest.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.XUnit.PerTest.UnitTests.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.XUnit.PerTest.UnitTests.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.XUnit.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.XUnit.UnitTests.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.Headless.XUnit.UnitTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.XUnit.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.XUnit.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.XUnit.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject b/.ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject b/.ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject b/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject
deleted file mode 100644
index 18f2aee75c..0000000000
--- a/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net462
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.v3.ncrunchproject
index 1733492c89..0bcc569d05 100644
--- a/.ncrunch/Avalonia.Markup.UnitTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Markup.UnitTests.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = net8.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.Xaml.Loader.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.Loader.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Markup.Xaml.Loader.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.Xaml.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.v3.ncrunchproject
index 1733492c89..0bcc569d05 100644
--- a/.ncrunch/Avalonia.Markup.Xaml.UnitTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = net8.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Markup.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Metal.v3.ncrunchproject b/.ncrunch/Avalonia.Metal.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Metal.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
index a079eefacf..0bcc569d05 100644
--- a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = netstandard2.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Native.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Native.net6.0.v3.ncrunchproject
deleted file mode 100644
index e8de009693..0000000000
--- a/.ncrunch/Avalonia.Native.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- True
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Native.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Native.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/Avalonia.Native.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Native.v3.ncrunchproject b/.ncrunch/Avalonia.Native.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Native.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.OpenGL.v3.ncrunchproject b/.ncrunch/Avalonia.OpenGL.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.OpenGL.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.ReactiveUI.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.ReactiveUI.UnitTests.v3.ncrunchproject
deleted file mode 100644
index 1733492c89..0000000000
--- a/.ncrunch/Avalonia.ReactiveUI.UnitTests.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Remote.Protocol.v3.ncrunchproject b/.ncrunch/Avalonia.Remote.Protocol.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Remote.Protocol.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.RenderTests.WpfCompare.v3.ncrunchproject b/.ncrunch/Avalonia.RenderTests.WpfCompare.v3.ncrunchproject
deleted file mode 100644
index 96fdff3555..0000000000
--- a/.ncrunch/Avalonia.RenderTests.WpfCompare.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0-windows
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
index 1733492c89..0bcc569d05 100644
--- a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = net8.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject
index 1733492c89..0bcc569d05 100644
--- a/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject
@@ -1,7 +1,5 @@
-
- TargetFramework = net8.0
-
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Skia.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Skia.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject
deleted file mode 100644
index 02eb0d211e..0000000000
--- a/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- False
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 02eb0d211e..0000000000
--- a/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- False
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Themes.Fluent.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Themes.Fluent.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index bc1af9a143..0000000000
--- a/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- ..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml
-
- False
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Themes.Simple.net8.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.v3.ncrunchproject
similarity index 69%
rename from .ncrunch/Avalonia.Themes.Simple.net8.0.v3.ncrunchproject
rename to .ncrunch/Avalonia.Themes.Simple.v3.ncrunchproject
index bc1af9a143..cf2b7e5426 100644
--- a/.ncrunch/Avalonia.Themes.Simple.net8.0.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Themes.Simple.v3.ncrunchproject
@@ -1,8 +1,8 @@
+ False
..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml
- False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Tizen.v3.ncrunchproject b/.ncrunch/Avalonia.Tizen.v3.ncrunchproject
deleted file mode 100644
index d2c880c578..0000000000
--- a/.ncrunch/Avalonia.Tizen.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0-tizen
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject
index 7a4ea98049..0bcc569d05 100644
--- a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = netstandard2.0
-
- False
+ False
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Vulkan.v3.ncrunchproject b/.ncrunch/Avalonia.Vulkan.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Vulkan.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.Automation.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.Automation.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Win32.Automation.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.Interoperability.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.Interoperability.v3.ncrunchproject
deleted file mode 100644
index 96fdff3555..0000000000
--- a/.ncrunch/Avalonia.Win32.Interoperability.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- TargetFramework = net8.0-windows
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
deleted file mode 100644
index 95a483b433..0000000000
--- a/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 95a483b433..0000000000
--- a/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.Win32.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.X11.v3.ncrunchproject b/.ncrunch/Avalonia.X11.v3.ncrunchproject
new file mode 100644
index 0000000000..0bcc569d05
--- /dev/null
+++ b/.ncrunch/Avalonia.X11.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.net6.0.v3.ncrunchproject
deleted file mode 100644
index 4fdcd169ae..0000000000
--- a/.ncrunch/Avalonia.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- DerivedFilesIncludedInWorkspace
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.net8.0.v3.ncrunchproject b/.ncrunch/Avalonia.net8.0.v3.ncrunchproject
deleted file mode 100644
index 4fdcd169ae..0000000000
--- a/.ncrunch/Avalonia.net8.0.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- DerivedFilesIncludedInWorkspace
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.netcoreapp2.0.v3.ncrunchproject
deleted file mode 100644
index 4fdcd169ae..0000000000
--- a/.ncrunch/Avalonia.netcoreapp2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- DerivedFilesIncludedInWorkspace
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 4fdcd169ae..0000000000
--- a/.ncrunch/Avalonia.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- DerivedFilesIncludedInWorkspace
-
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.net461.v3.ncrunchproject b/.ncrunch/Avalonia.v3.ncrunchproject
similarity index 63%
rename from .ncrunch/Avalonia.net461.v3.ncrunchproject
rename to .ncrunch/Avalonia.v3.ncrunchproject
index 4fdcd169ae..b12ed97abf 100644
--- a/.ncrunch/Avalonia.net461.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.v3.ncrunchproject
@@ -3,5 +3,6 @@
DerivedFilesIncludedInWorkspace
+ False
\ No newline at end of file
diff --git a/.ncrunch/BindingDemo.v3.ncrunchproject b/.ncrunch/BindingDemo.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/BindingDemo.v3.ncrunchproject
+++ b/.ncrunch/BindingDemo.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.Android.v3.ncrunchproject b/.ncrunch/ControlCatalog.Android.v3.ncrunchproject
index 1335822600..319cd523ce 100644
--- a/.ncrunch/ControlCatalog.Android.v3.ncrunchproject
+++ b/.ncrunch/ControlCatalog.Android.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0-android34.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject b/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject
deleted file mode 100644
index 7e6955b244..0000000000
--- a/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0-browser
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject b/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject
index 7e6955b244..319cd523ce 100644
--- a/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject
+++ b/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0-browser
-
True
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.Desktop.v3.ncrunchproject b/.ncrunch/ControlCatalog.Desktop.v3.ncrunchproject
index ea3a821a00..319cd523ce 100644
--- a/.ncrunch/ControlCatalog.Desktop.v3.ncrunchproject
+++ b/.ncrunch/ControlCatalog.Desktop.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net461
-
True
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject b/.ncrunch/ControlCatalog.MacCatalyst.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject
rename to .ncrunch/ControlCatalog.MacCatalyst.v3.ncrunchproject
diff --git a/.ncrunch/ControlCatalog.NetCore.v3.ncrunchproject b/.ncrunch/ControlCatalog.NetCore.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/ControlCatalog.NetCore.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.Tizen.v3.ncrunchproject b/.ncrunch/ControlCatalog.Tizen.v3.ncrunchproject
deleted file mode 100644
index d2c880c578..0000000000
--- a/.ncrunch/ControlCatalog.Tizen.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0-tizen
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.iOS.v3.ncrunchproject b/.ncrunch/ControlCatalog.iOS.v3.ncrunchproject
index 6ae5567f23..319cd523ce 100644
--- a/.ncrunch/ControlCatalog.iOS.v3.ncrunchproject
+++ b/.ncrunch/ControlCatalog.iOS.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0-ios17.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.net8.0.v3.ncrunchproject b/.ncrunch/ControlCatalog.net8.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/ControlCatalog.net8.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.netstandard2.0.v3.ncrunchproject b/.ncrunch/ControlCatalog.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/ControlCatalog.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject b/.ncrunch/ControlCatalog.tvOS.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject
rename to .ncrunch/ControlCatalog.tvOS.v3.ncrunchproject
diff --git a/.ncrunch/ControlSamples.v3.ncrunchproject b/.ncrunch/ControlSamples.v3.ncrunchproject
index 00c1cea8f4..319cd523ce 100644
--- a/.ncrunch/ControlSamples.v3.ncrunchproject
+++ b/.ncrunch/ControlSamples.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = netstandard2.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/DevAnalyzers.v3.ncrunchproject b/.ncrunch/DevAnalyzers.v3.ncrunchproject
index a079eefacf..08b7dfa6ee 100644
--- a/.ncrunch/DevAnalyzers.v3.ncrunchproject
+++ b/.ncrunch/DevAnalyzers.v3.ncrunchproject
@@ -3,5 +3,6 @@
TargetFramework = netstandard2.0
+ False
\ No newline at end of file
diff --git a/.ncrunch/DevGenerators.v3.ncrunchproject b/.ncrunch/DevGenerators.v3.ncrunchproject
index a079eefacf..08b7dfa6ee 100644
--- a/.ncrunch/DevGenerators.v3.ncrunchproject
+++ b/.ncrunch/DevGenerators.v3.ncrunchproject
@@ -3,5 +3,6 @@
TargetFramework = netstandard2.0
+ False
\ No newline at end of file
diff --git a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Generators.Sandbox.v3.ncrunchproject b/.ncrunch/Generators.Sandbox.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/Generators.Sandbox.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/GpuInterop.v3.ncrunchproject b/.ncrunch/GpuInterop.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/GpuInterop.v3.ncrunchproject
+++ b/.ncrunch/GpuInterop.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/IntegrationTestApp.v3.ncrunchproject b/.ncrunch/IntegrationTestApp.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/IntegrationTestApp.v3.ncrunchproject
+++ b/.ncrunch/IntegrationTestApp.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.Android.v3.ncrunchproject b/.ncrunch/MobileSandbox.Android.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.Android.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.Browser.v3.ncrunchproject b/.ncrunch/MobileSandbox.Browser.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.Browser.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject b/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject b/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject b/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject b/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.v3.ncrunchproject b/.ncrunch/MobileSandbox.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/MobileSandbox.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/NativeEmbedSample.v3.ncrunchproject b/.ncrunch/NativeEmbedSample.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/NativeEmbedSample.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/PInvoke.v3.ncrunchproject b/.ncrunch/PInvoke.v3.ncrunchproject
index 00c1cea8f4..95a483b433 100644
--- a/.ncrunch/PInvoke.v3.ncrunchproject
+++ b/.ncrunch/PInvoke.v3.ncrunchproject
@@ -1,8 +1,3 @@
-
-
- TargetFramework = netstandard2.0
-
- True
-
+
\ No newline at end of file
diff --git a/.ncrunch/PlatformSanityChecks.v3.ncrunchproject b/.ncrunch/PlatformSanityChecks.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/PlatformSanityChecks.v3.ncrunchproject
+++ b/.ncrunch/PlatformSanityChecks.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/Previewer.v3.ncrunchproject b/.ncrunch/Previewer.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/Previewer.v3.ncrunchproject
+++ b/.ncrunch/Previewer.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/ReactiveUIDemo.v3.ncrunchproject b/.ncrunch/ReactiveUIDemo.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/ReactiveUIDemo.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/RemoteDemo.v3.ncrunchproject b/.ncrunch/RemoteDemo.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/RemoteDemo.v3.ncrunchproject
+++ b/.ncrunch/RemoteDemo.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/RenderDemo.v3.ncrunchproject b/.ncrunch/RenderDemo.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/RenderDemo.v3.ncrunchproject
+++ b/.ncrunch/RenderDemo.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject
index 1335822600..319cd523ce 100644
--- a/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject
+++ b/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0-android34.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject
+++ b/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject
index 6ae5567f23..319cd523ce 100644
--- a/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject
+++ b/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0-ios17.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/SafeAreaDemo.v3.ncrunchproject
+++ b/.ncrunch/SafeAreaDemo.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/Sandbox.v3.ncrunchproject b/.ncrunch/Sandbox.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/Sandbox.v3.ncrunchproject
+++ b/.ncrunch/Sandbox.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/SingleProjectSandbox.v3.ncrunchproject b/.ncrunch/SingleProjectSandbox.v3.ncrunchproject
deleted file mode 100644
index 3b82c33f1c..0000000000
--- a/.ncrunch/SingleProjectSandbox.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net8.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject b/.ncrunch/TextTestApp.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject
rename to .ncrunch/TextTestApp.v3.ncrunchproject
diff --git a/.ncrunch/UnloadableAssemblyLoadContext.v3.ncrunchproject b/.ncrunch/UnloadableAssemblyLoadContext.v3.ncrunchproject
deleted file mode 100644
index f074de260d..0000000000
--- a/.ncrunch/UnloadableAssemblyLoadContext.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net7.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/UnloadableAssemblyLoadContextPlug.v3.ncrunchproject b/.ncrunch/UnloadableAssemblyLoadContextPlug.v3.ncrunchproject
deleted file mode 100644
index f074de260d..0000000000
--- a/.ncrunch/UnloadableAssemblyLoadContextPlug.v3.ncrunchproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- TargetFramework = net7.0
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/VirtualizationDemo.v3.ncrunchproject b/.ncrunch/VirtualizationDemo.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/VirtualizationDemo.v3.ncrunchproject
+++ b/.ncrunch/VirtualizationDemo.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.ncrunch/WindowsInteropTest.v3.ncrunchproject b/.ncrunch/WindowsInteropTest.v3.ncrunchproject
index 484445df0e..319cd523ce 100644
--- a/.ncrunch/WindowsInteropTest.v3.ncrunchproject
+++ b/.ncrunch/WindowsInteropTest.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0-windows
-
True
\ No newline at end of file
diff --git a/.ncrunch/XEmbedSample.net8.0.v3.ncrunchproject b/.ncrunch/XEmbedSample.net8.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/XEmbedSample.net8.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/XEmbedSample.netstandard2.0.v3.ncrunchproject b/.ncrunch/XEmbedSample.netstandard2.0.v3.ncrunchproject
deleted file mode 100644
index 319cd523ce..0000000000
--- a/.ncrunch/XEmbedSample.netstandard2.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- True
-
-
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject b/.ncrunch/XEmbedSample.v3.ncrunchproject
similarity index 100%
rename from .ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject
rename to .ncrunch/XEmbedSample.v3.ncrunchproject
diff --git a/.ncrunch/_build.v3.ncrunchproject b/.ncrunch/_build.v3.ncrunchproject
index 3b82c33f1c..319cd523ce 100644
--- a/.ncrunch/_build.v3.ncrunchproject
+++ b/.ncrunch/_build.v3.ncrunchproject
@@ -1,8 +1,5 @@
-
- TargetFramework = net8.0
-
True
\ No newline at end of file
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index 4107d5b1fc..e91aa1433c 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -35,6 +35,7 @@
"CreateNugetPackages",
"DownloadApiBaselinePackages",
"GenerateCppHeaders",
+ "InitDnx",
"OutputApiDiff",
"OutputVersion",
"Package",
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 2b99dc87e9..b861cf00c5 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -1,4 +1,4 @@
-{
+{
"solution": {
"path": "Avalonia.sln",
"projects": [
@@ -22,7 +22,6 @@
"src\\Avalonia.Controls\\Avalonia.Controls.csproj",
"src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj",
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
- "src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj",
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
@@ -35,6 +34,7 @@
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
+ "src\\HarfBuzz\\Avalonia.HarfBuzz\\Avalonia.HarfBuzz.csproj",
"src\\Headless\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
@@ -42,7 +42,9 @@
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
"src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj",
"src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj",
- "src\\tools\\Avalonia.Analyzers\\Avalonia.Analyzers.csproj",
+ "src\\tools\\Avalonia.Analyzers.CodeFixes.CSharp\\Avalonia.Analyzers.CodeFixes.CSharp.csproj",
+ "src\\tools\\Avalonia.Analyzers.CSharp\\Avalonia.Analyzers.CSharp.csproj",
+ "src\\tools\\Avalonia.Analyzers.VisualBasic\\Avalonia.Analyzers.VisualBasic.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
diff --git a/Avalonia.sln b/Avalonia.sln
index 663f8c3319..06c3e051ac 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Av
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Simple", "src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.UnitTests", "tests\Avalonia.Controls.UnitTests\Avalonia.Controls.UnitTests.csproj", "{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}"
@@ -36,8 +34,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE
src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs
src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs
src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs
- src\Shared\StringCompatibilityExtensions.cs = src\Shared\StringCompatibilityExtensions.cs
src\Shared\StreamCompatibilityExtensions.cs = src\Shared\StreamCompatibilityExtensions.cs
+ src\Shared\StringCompatibilityExtensions.cs = src\Shared\StringCompatibilityExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Markup\Avalonia.Markup\Avalonia.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}"
@@ -82,6 +80,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\A
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
+ build\AnalyzerProject.targets = build\AnalyzerProject.targets
build\AvaloniaPublicKey.props = build\AvaloniaPublicKey.props
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
@@ -107,9 +106,7 @@ 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
- build\AnalyzerProject.targets = build\AnalyzerProject.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
@@ -206,7 +203,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers.CSharp", "src\tools\Avalonia.Analyzers.CSharp\Avalonia.Analyzers.CSharp.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
ProjectSection(SolutionItems) = preProject
@@ -277,6 +274,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.MacCatalyst"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.tvOS", "samples\ControlCatalog.tvOS\ControlCatalog.tvOS.csproj", "{14342787-B4EF-4076-8C91-BA6C523DE8DF}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HarfBuzz", "HarfBuzz", "{7670D720-6E84-4AFC-8331-A5C399481905}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.HarfBuzz", "src\HarfBuzz\Avalonia.HarfBuzz\Avalonia.HarfBuzz.csproj", "{E2BFA463-6402-4EF8-8945-FD9A10A914D1}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit.PerAssembly.UnitTests", "tests\Avalonia.Headless.NUnit.PerAssembly.UnitTests\Avalonia.Headless.NUnit.PerAssembly.UnitTests.csproj", "{A175EFAE-476C-4DAA-87D5-742C18CFCC27}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit.PerTest.UnitTests", "tests\Avalonia.Headless.NUnit.PerTest.UnitTests\Avalonia.Headless.NUnit.PerTest.UnitTests.csproj", "{09EC467F-0F25-4E6F-A836-2BAEC8F6AB0C}"
@@ -287,6 +288,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit.Per
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.IntegrationTests.Win32", "tests\Avalonia.IntegrationTests.Win32\Avalonia.IntegrationTests.Win32.csproj", "{11522B0D-BF31-42D5-8FC5-41E58F319AF9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Analyzers.CodeFixes.CSharp", "src\tools\Avalonia.Analyzers.CodeFixes.CSharp\Avalonia.Analyzers.CodeFixes.CSharp.csproj", "{FDFB9C25-552D-420B-9D4A-DB0BB6472239}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Analyzers.VisualBasic", "src\tools\Avalonia.Analyzers.VisualBasic\Avalonia.Analyzers.VisualBasic.csproj", "{A7644C3B-B843-44F1-9940-560D56CB0936}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -309,10 +314,6 @@ Global
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}.Release|Any CPU.Build.0 = Release|Any CPU
- {7062AE20-5DCC-4442-9645-8195BDECE63E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7062AE20-5DCC-4442-9645-8195BDECE63E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7062AE20-5DCC-4442-9645-8195BDECE63E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7062AE20-5DCC-4442-9645-8195BDECE63E}.Release|Any CPU.Build.0 = Release|Any CPU
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -645,6 +646,10 @@ Global
{14342787-B4EF-4076-8C91-BA6C523DE8DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14342787-B4EF-4076-8C91-BA6C523DE8DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14342787-B4EF-4076-8C91-BA6C523DE8DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2BFA463-6402-4EF8-8945-FD9A10A914D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2BFA463-6402-4EF8-8945-FD9A10A914D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2BFA463-6402-4EF8-8945-FD9A10A914D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2BFA463-6402-4EF8-8945-FD9A10A914D1}.Release|Any CPU.Build.0 = Release|Any CPU
{A175EFAE-476C-4DAA-87D5-742C18CFCC27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A175EFAE-476C-4DAA-87D5-742C18CFCC27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A175EFAE-476C-4DAA-87D5-742C18CFCC27}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -665,6 +670,14 @@ Global
{11522B0D-BF31-42D5-8FC5-41E58F319AF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11522B0D-BF31-42D5-8FC5-41E58F319AF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11522B0D-BF31-42D5-8FC5-41E58F319AF9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7644C3B-B843-44F1-9940-560D56CB0936}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7644C3B-B843-44F1-9940-560D56CB0936}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7644C3B-B843-44F1-9940-560D56CB0936}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {A7644C3B-B843-44F1-9940-560D56CB0936}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {FDFB9C25-552D-420B-9D4A-DB0BB6472239}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FDFB9C25-552D-420B-9D4A-DB0BB6472239}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FDFB9C25-552D-420B-9D4A-DB0BB6472239}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {FDFB9C25-552D-420B-9D4A-DB0BB6472239}.Debug|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -744,11 +757,14 @@ Global
{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{DE3C28DD-B602-4750-831D-345102A54CA0} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{14342787-B4EF-4076-8C91-BA6C523DE8DF} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {E2BFA463-6402-4EF8-8945-FD9A10A914D1} = {7670D720-6E84-4AFC-8331-A5C399481905}
{A175EFAE-476C-4DAA-87D5-742C18CFCC27} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{09EC467F-0F25-4E6F-A836-2BAEC8F6AB0C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{342D2657-2F84-493C-B74B-9D2CAE5D9DAB} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{26918642-829D-4FA2-B60A-BE8D83F4E063} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{11522B0D-BF31-42D5-8FC5-41E58F319AF9} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {A7644C3B-B843-44F1-9940-560D56CB0936} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+ {FDFB9C25-552D-420B-9D4A-DB0BB6472239} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution
index 95f316f8cf..f675b66f7b 100644
--- a/Avalonia.v3.ncrunchsolution
+++ b/Avalonia.v3.ncrunchsolution
@@ -10,9 +10,10 @@
True
RunApiCompat = false
- TargetFrameworks = net8.0;netstandard2.0
+ TargetFrameworks = net10.0
False
+ True
.ncrunch
True
True
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index fde2931bf9..51c5c72372 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -25,6 +25,9 @@ community include:
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
+* Respecting that contributors and maintainers have invested significant effort in building this project
+* Keeping discussions focused on improving Avalonia and its ecosystem
+* When discussing alternatives, doing so with relevant technical context that advances the conversation
Examples of unacceptable behavior include:
@@ -36,6 +39,9 @@ Examples of unacceptable behavior include:
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
+* Repeated unsolicited promotion, recruitment, or calls to action for external projects, products, or services in Avalonia’s official channels, particularly when it derails technical discussion or continues after redirection
+* Using Avalonia’s official channels to organise or coordinate work for unrelated projects rather than collaborating on Avalonia
+* Posting promotional content primarily to drive traffic, sales, or contributors, rather than to contribute to the topic at hand
## Enforcement Responsibilities
@@ -44,6 +50,8 @@ acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
+Avalonia’s official infrastructure exists to support Avalonia’s development and community. Constructive technical criticism and relevant comparisons are welcome. What is not welcome is repeated promotional activity, recruitment, or organising unrelated projects within Avalonia’s official channels.
+
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
@@ -72,6 +80,13 @@ reporter of any incident.
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
+### Specific Guidance: Infrastructure Misuse and Promotion
+**Community Impact:** A pattern of using Avalonia’s official channels primarily to promote external projects, products, or services, recruit contributors, or coordinate unrelated work, especially where it derails ongoing technical discussion or continues after a moderator request to stop.
+
+**Consequence:** Moderators will remove promotional content immediately to keep discussions focused. If the behaviour appears unintentional or isolated, moderators will follow up privately with guidance and redirect to appropriate venues (e.g., AvaloniaCommunity org, personal blogs). Repeated violations after guidance will result in a formal warning and, if continued, restriction from project platforms up to and including a permanent ban.
+
+*Note: This guidance addresses patterns of disruption and misuse of shared channels. It does not prohibit technical criticism, benchmarks, or mentioning alternatives when relevant and explained with technical context.*
+
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
@@ -106,9 +121,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
+**Community Impact:** Demonstrating a pattern of violation of community standards, including sustained inappropriate behaviour, harassment of an individual, aggression towards or disparagement of classes of individuals, or persistent misuse of project infrastructure after warnings.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
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.Android.nupkg.xml b/api/Avalonia.Android.nupkg.xml
new file mode 100644
index 0000000000..6ffd08efe1
--- /dev/null
+++ b/api/Avalonia.Android.nupkg.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ CP0001
+ T:Avalonia.Android.Platform.Specific.IAndroidView
+ baseline/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+ current/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+
+
+ CP0008
+ T:Avalonia.Android.AvaloniaActivity
+ baseline/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+ current/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+
+
+ CP0008
+ T:Avalonia.Android.AvaloniaMainActivity
+ baseline/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+ current/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+
+
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.Headless.nupkg.xml b/api/Avalonia.Headless.nupkg.xml
new file mode 100644
index 0000000000..229047057a
--- /dev/null
+++ b/api/Avalonia.Headless.nupkg.xml
@@ -0,0 +1,40 @@
+
+
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.KeyPress(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.KeyRelease(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.KeyPress(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.KeyRelease(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+
+
diff --git a/api/Avalonia.Skia.nupkg.xml b/api/Avalonia.Skia.nupkg.xml
new file mode 100644
index 0000000000..eda59fc334
--- /dev/null
+++ b/api/Avalonia.Skia.nupkg.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ CP0008
+ T:Avalonia.Skia.ISkiaGpu
+ baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+ current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+
+
+ CP0008
+ T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext
+ baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+ current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+
+
+ CP0008
+ T:Avalonia.Skia.ISkiaGpu
+ baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+ current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+
+
+ CP0008
+ T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext
+ baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+ current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+
+
diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 84a9f4b163..1c9ac0d5e9 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1,12 +1,204 @@
-
+
+
+ CP0001
+ T:Avalonia.Animation.CustomAnimatorBase
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Animation.CustomAnimatorBase`1
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Animation.Easings.CubicBezierEasing
+ 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.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Diagnostics.AppliedStyle
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Diagnostics.StyledElementExtensions
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Diagnostics.StyleDiagnostics
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.DataObjectExtensions
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.IDataObject
+ 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/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Platform.IGeometryContext2
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Platform.IOptionalFeatureProvider
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Platform.IReadableBitmapWithAlphaImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Platform.OptionalFeatureProviderExtensions
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Styling.IStyleable
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Utilities.StringTokenizer
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetimeOptions
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.ApplicationLifetimes.IActivatableApplicationLifetime
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.FileDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.FileDialogFilter
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.FileSystemDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Generators.TreeContainerIndex
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Generators.TreeItemContainerGenerator
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.NativeMenuItemToggleType
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.OpenFileDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.OpenFolderDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Platform.ISystemDialogImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.IScrollable
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.SaveFileDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.SystemDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Platform.IApplicationPlatformEvents
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Markup.Xaml.ConstructorArgumentAttribute
+ baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll
+ current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll
+
CP0001
T:Avalonia.Media.Fonts.FontFamilyLoader
@@ -14,260 +206,1910 @@
current/Avalonia/lib/net6.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
+ CP0001
+ T:Avalonia.Animation.CustomAnimatorBase
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Animation.CustomAnimatorBase`1
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Animation.Easings.CubicBezierEasing
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.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
+
+
+ CP0001
+ T:Avalonia.Diagnostics.AppliedStyle
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Diagnostics.StyledElementExtensions
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Diagnostics.StyleDiagnostics
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.DataObjectExtensions
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.IDataObject
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.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
+
+
+ CP0001
+ T:Avalonia.Platform.IGeometryContext2
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Platform.IOptionalFeatureProvider
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Platform.IReadableBitmapWithAlphaImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Platform.OptionalFeatureProviderExtensions
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Styling.IStyleable
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.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
+
+
+ CP0001
+ T:Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetimeOptions
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.ApplicationLifetimes.IActivatableApplicationLifetime
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.FileDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.FileDialogFilter
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.FileSystemDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Generators.TreeContainerIndex
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Generators.TreeItemContainerGenerator
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.NativeMenuItemToggleType
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.OpenFileDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.OpenFolderDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Platform.ISystemDialogImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.IScrollable
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.SaveFileDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.SystemDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Platform.IApplicationPlatformEvents
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Markup.Xaml.ConstructorArgumentAttribute
+ baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll
+ current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll
+
+
+ CP0001
+ T:Avalonia.Media.Fonts.FontFamilyLoader
+ baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
+ current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Controls.ResourcesChangedEventArgs.Empty
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Data.BindingPriority.TemplatedParent
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Input.DataFormats.FileNames
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Input.DataFormats.Files
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Input.DataFormats.Text
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Media.DrawingImage.ViewboxProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Media.RadialGradientBrush.RadiusProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Animation.Animation.SetAnimator(Avalonia.Animation.IAnimationSetter,Avalonia.Animation.CustomAnimatorBase)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.BindingBase,System.Object)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ResourcesChangedEventArgs.#ctor
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Data.CompiledBindingPathBuilder.SetRawSource(System.Object)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Data.ReflectionBinding.#ctor(System.String,Avalonia.Data.BindingMode)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.Contains(System.String)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.Get(System.String)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.GetDataFormats
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.Set(System.String,System.Object)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DragDrop.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DragEventArgs.#ctor(Avalonia.Interactivity.RoutedEvent{Avalonia.Input.DragEventArgs},Avalonia.Input.IDataObject,Avalonia.Interactivity.Interactive,Avalonia.Point,Avalonia.Input.KeyModifiers)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DragEventArgs.get_Data
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.Input.Platform.IClipboard.GetDataAsync(System.String)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.GetFormatsAsync
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.GetTextAsync
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.SetDataObjectAsync(Avalonia.Input.IDataObject)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.SetTextAsync(System.String)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataObjectAsync
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawDragEvent.#ctor(Avalonia.Input.Raw.IDragDropDevice,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IInputRoot,Avalonia.Point,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawDragEvent.get_Data
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawKeyEventArgs.#ctor(Avalonia.Input.IInputDevice,System.UInt64,Avalonia.Input.IInputRoot,Avalonia.Input.Raw.RawKeyEventType,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers,Avalonia.Input.PhysicalKey,Avalonia.Input.KeyDeviceType,System.String)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawKeyEventArgs.#ctor(Avalonia.Input.IInputDevice,System.UInt64,Avalonia.Input.IInputRoot,Avalonia.Input.Raw.RawKeyEventType,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers,Avalonia.Input.PhysicalKey,System.String)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawKeyEventArgs.#ctor(Avalonia.Input.IKeyboardDevice,System.UInt64,Avalonia.Input.IInputRoot,Avalonia.Input.Raw.RawKeyEventType,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.Color.ToUint32
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.DrawingContext.PushPostTransform(Avalonia.Matrix)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.DrawingContext.PushPreTransform(Avalonia.Matrix)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.DrawingContext.PushTransformContainer
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.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.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.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.Immutable.ImmutableRadialGradientBrush.get_Radius
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.IRadialGradientBrush.get_Radius
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.RadialGradientBrush.get_Radius
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.RadialGradientBrush.set_Radius(System.Double)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.StreamGeometryContext.LineTo(Avalonia.Point)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.TextCollapsingProperties.CreateCollapsedRuns(Avalonia.Media.TextFormatting.TextLine,System.Int32,Avalonia.Media.FlowDirection,Avalonia.Media.TextFormatting.TextRun)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})
+ 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.GlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)
+ 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.GlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.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.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.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.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.IPlatformRenderInterfaceContext.CreateOffscreenRenderTarget(Avalonia.PixelSize,System.Double)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.get_ImportCompeted
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}})
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.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.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.ContextMenu.PlacementModeProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.NativeMenuBar.EnableMenuItemClickForwardingProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.NativeMenuItem.ToggleTypeProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.Popup.PlacementModeProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.ToggleButton.CheckedEvent
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.ToggleButton.IndeterminateEvent
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.ToggleButton.UncheckedEvent
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.TextBox.LetterSpacingProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.AppBuilder.get_LifetimeOverride
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Application.add_UrlsOpened(System.EventHandler{Avalonia.UrlOpenedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Application.remove_UrlsOpened(System.EventHandler{Avalonia.UrlOpenedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ContextMenu.get_PlacementMode
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ContextMenu.set_PlacementMode(Avalonia.Controls.PlacementMode)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.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.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.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.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.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.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.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.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.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.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.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.Controls.Generators.ItemContainerGenerator.ContainerFromIndex(System.Int32)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Generators.ItemContainerGenerator.IndexFromContainer(Avalonia.Controls.Control)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ItemsControl.ItemsControlFromItemContaner(Avalonia.Controls.Control)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.NativeMenuBar.SetEnableMenuItemClickForwarding(Avalonia.Controls.MenuItem,System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.NativeMenuItem.get_ToggleType
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Platform.IInsetsManager.get_DisplayEdgeToEdge
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Platform.IInsetsManager.set_DisplayEdgeToEdge(System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Platform.InsetsManagerBase.get_DisplayEdgeToEdge
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Platform.InsetsManagerBase.set_DisplayEdgeToEdge(System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.CreatePopupHost(Avalonia.Visual,Avalonia.IAvaloniaDependencyResolver)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.Popup.get_PlacementMode
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.Popup.set_PlacementMode(Avalonia.Controls.PlacementMode)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.PopupRoot.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.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.Controls.Primitives.ToggleButton.add_Checked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.add_Indeterminate(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.add_Unchecked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.OnChecked(Avalonia.Interactivity.RoutedEventArgs)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.OnIndeterminate(Avalonia.Interactivity.RoutedEventArgs)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.OnUnchecked(Avalonia.Interactivity.RoutedEventArgs)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.remove_Checked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.remove_Indeterminate(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.ToggleButton.remove_Unchecked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TabItem.SubscribeToOwnerProperties(Avalonia.AvaloniaObject)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TreeView.get_ItemContainerGenerator
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.Platform.Screen.get_PixelDensity
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Platform.Screen.get_Primary
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.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.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.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.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.Screen.set_WorkingArea(Avalonia.PixelRect)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Dialogs.ManagedFileDialogExtensions.ShowManagedAsync(Avalonia.Controls.OpenFileDialog,Avalonia.Controls.Window,Avalonia.Dialogs.ManagedFileDialogOptions)
+ baseline/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+
+
+ CP0002
+ M:Avalonia.Dialogs.ManagedFileDialogExtensions.ShowManagedAsync``1(Avalonia.Controls.OpenFileDialog,Avalonia.Controls.Window,Avalonia.Dialogs.ManagedFileDialogOptions)
+ baseline/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+
+
+ CP0002
+ M:Avalonia.Data.Binding.#ctor(System.String,Avalonia.Data.BindingMode)
+ baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll
+ current/Avalonia/lib/net10.0/Avalonia.Markup.dll
+
+
+ CP0002
+ M:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension.#ctor(System.String,Avalonia.Data.BindingMode)
+ baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll
+ current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll
+
+
+ CP0002
+ M:Avalonia.Markup.Xaml.XamlLoadException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
+ baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll
+ current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll
+
+
+ CP0002
+ 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.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.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)
+ baseline/Avalonia/lib/net6.0/Avalonia.Base.dll
+ current/Avalonia/lib/net6.0/Avalonia.Base.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
+
+
+ CP0002
+ 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
+ F:Avalonia.Controls.ResourcesChangedEventArgs.Empty
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Data.BindingPriority.TemplatedParent
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Input.DataFormats.FileNames
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Input.DataFormats.Files
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Input.DataFormats.Text
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Media.DrawingImage.ViewboxProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ F:Avalonia.Media.RadialGradientBrush.RadiusProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Animation.Animation.SetAnimator(Avalonia.Animation.IAnimationSetter,Avalonia.Animation.CustomAnimatorBase)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.BindingBase,System.Object)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ResourcesChangedEventArgs.#ctor
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Data.CompiledBindingPathBuilder.SetRawSource(System.Object)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Data.ReflectionBinding.#ctor(System.String,Avalonia.Data.BindingMode)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.Contains(System.String)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.Get(System.String)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.GetDataFormats
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DataObject.Set(System.String,System.Object)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DragDrop.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DragEventArgs.#ctor(Avalonia.Interactivity.RoutedEvent{Avalonia.Input.DragEventArgs},Avalonia.Input.IDataObject,Avalonia.Interactivity.Interactive,Avalonia.Point,Avalonia.Input.KeyModifiers)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.DragEventArgs.get_Data
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.Input.Platform.IClipboard.GetDataAsync(System.String)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.GetFormatsAsync
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.GetTextAsync
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.SetDataObjectAsync(Avalonia.Input.IDataObject)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.SetTextAsync(System.String)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataObjectAsync
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawDragEvent.#ctor(Avalonia.Input.Raw.IDragDropDevice,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IInputRoot,Avalonia.Point,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawDragEvent.get_Data
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawKeyEventArgs.#ctor(Avalonia.Input.IInputDevice,System.UInt64,Avalonia.Input.IInputRoot,Avalonia.Input.Raw.RawKeyEventType,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers,Avalonia.Input.PhysicalKey,Avalonia.Input.KeyDeviceType,System.String)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawKeyEventArgs.#ctor(Avalonia.Input.IInputDevice,System.UInt64,Avalonia.Input.IInputRoot,Avalonia.Input.Raw.RawKeyEventType,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers,Avalonia.Input.PhysicalKey,System.String)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.Raw.RawKeyEventArgs.#ctor(Avalonia.Input.IKeyboardDevice,System.UInt64,Avalonia.Input.IInputRoot,Avalonia.Input.Raw.RawKeyEventType,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.Color.ToUint32
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.DrawingContext.PushPostTransform(Avalonia.Matrix)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.DrawingContext.PushPreTransform(Avalonia.Matrix)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.DrawingContext.PushTransformContainer
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.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.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.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.Immutable.ImmutableRadialGradientBrush.get_Radius
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.IRadialGradientBrush.get_Radius
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.RadialGradientBrush.get_Radius
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.RadialGradientBrush.set_Radius(System.Double)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.StreamGeometryContext.LineTo(Avalonia.Point)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.TextCollapsingProperties.CreateCollapsedRuns(Avalonia.Media.TextFormatting.TextLine,System.Int32,Avalonia.Media.FlowDirection,Avalonia.Media.TextFormatting.TextRun)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})
+ 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.GlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)
+ 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.GlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.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.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.Platform.IGeometryContext.LineTo(Avalonia.Point)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.Platform.IPlatformRenderInterfaceContext.CreateOffscreenRenderTarget(Avalonia.PixelSize,System.Double)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.get_ImportCompeted
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}})
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ 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.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.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.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
+ F:Avalonia.Controls.ContextMenu.PlacementModeProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.NativeMenuItem.ToggleTypeProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.Popup.PlacementModeProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.ToggleButton.CheckedEvent
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.ToggleButton.IndeterminateEvent
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.Primitives.ToggleButton.UncheckedEvent
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.TextBlock.LetterSpacingProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ F:Avalonia.Controls.TextBox.LetterSpacingProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.AppBuilder.get_LifetimeOverride
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Application.add_UrlsOpened(System.EventHandler{Avalonia.UrlOpenedEventArgs})
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Application.remove_UrlsOpened(System.EventHandler{Avalonia.UrlOpenedEventArgs})
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ContextMenu.get_PlacementMode
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.ContextMenu.set_PlacementMode(Avalonia.Controls.PlacementMode)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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.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.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.Controls.Design.GetPreviewWith(Avalonia.Styling.IStyle)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ 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
+ 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
+ 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
- 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.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
- F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache
- baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
- current/Avalonia/lib/net10.0/Avalonia.Base.dll
+ 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.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)
- baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
- current/Avalonia/lib/net10.0/Avalonia.Base.dll
+ 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.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)
- baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
- current/Avalonia/lib/net10.0/Avalonia.Base.dll
+ 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.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect)
- baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
- current/Avalonia/lib/net10.0/Avalonia.Base.dll
+ 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.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
+ M:Avalonia.Controls.Generators.ItemContainerGenerator.ContainerFromIndex(System.Int32)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- F:Avalonia.Controls.TextBlock.LetterSpacingProperty
- baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
- current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ M:Avalonia.Controls.Generators.ItemContainerGenerator.IndexFromContainer(Avalonia.Controls.Control)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- F:Avalonia.Controls.TextBox.LetterSpacingProperty
- baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
- current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ M:Avalonia.Controls.ItemsControl.ItemsControlFromItemContaner(Avalonia.Controls.Control)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object)
- baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
- current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ M:Avalonia.Controls.NativeMenuBar.SetEnableMenuItemClickForwarding(Avalonia.Controls.MenuItem,System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.NativeMenuItem.get_ToggleType
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Platform.IInsetsManager.get_DisplayEdgeToEdge
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Styling.IStyle)
- baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
- current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ M:Avalonia.Controls.Platform.IInsetsManager.set_DisplayEdgeToEdge(System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Platform.InsetsManagerBase.get_DisplayEdgeToEdge
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Platform.InsetsManagerBase.set_DisplayEdgeToEdge(System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.CreatePopupHost(Avalonia.Visual,Avalonia.IAvaloniaDependencyResolver)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.Popup.get_PlacementMode
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.Popup.set_PlacementMode(Avalonia.Controls.PlacementMode)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.PopupRoot.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache
- 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/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ 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.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)
- baseline/Avalonia/lib/net6.0/Avalonia.Base.dll
- current/Avalonia/lib/net6.0/Avalonia.Base.dll
+ M:Avalonia.Controls.Primitives.ToggleButton.add_Checked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ 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/net6.0/Avalonia.Base.dll
- current/Avalonia/lib/net6.0/Avalonia.Base.dll
+ M:Avalonia.Controls.Primitives.ToggleButton.add_Indeterminate(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.ToggleButton.add_Unchecked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache
- baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
- current/Avalonia/lib/net8.0/Avalonia.Base.dll
+ M:Avalonia.Controls.Primitives.ToggleButton.OnChecked(Avalonia.Interactivity.RoutedEventArgs)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.ToggleButton.OnIndeterminate(Avalonia.Interactivity.RoutedEventArgs)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- 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
+ M:Avalonia.Controls.Primitives.ToggleButton.OnUnchecked(Avalonia.Interactivity.RoutedEventArgs)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect)
- baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
- current/Avalonia/lib/net8.0/Avalonia.Base.dll
+ M:Avalonia.Controls.Primitives.ToggleButton.remove_Checked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ 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.Primitives.ToggleButton.remove_Indeterminate(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
+ 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.Primitives.ToggleButton.remove_Unchecked(System.EventHandler{Avalonia.Interactivity.RoutedEventArgs})
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- F:Avalonia.Controls.TextBox.LetterSpacingProperty
+ M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object)
+ M:Avalonia.Controls.TabItem.SubscribeToOwnerProperties(Avalonia.AvaloniaObject)
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.GetDataContext(Avalonia.Controls.Templates.IDataTemplate)
+ M:Avalonia.Controls.TreeView.get_ItemContainerGenerator
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Controls.Templates.IDataTemplate)
+ 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.Design.GetPreviewWith(Avalonia.Styling.IStyle)
+ M:Avalonia.Platform.Screen.get_PixelDensity
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.SetDataContext(Avalonia.Controls.Templates.IDataTemplate,System.Object)
+ M:Avalonia.Platform.Screen.get_Primary
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
CP0002
- M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})
+ 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.Controls.Design.SetPreviewWith(Avalonia.Controls.ResourceDictionary,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})
+ 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.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.Control)
+ 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.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})
+ 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.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.Control)
+ 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.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})
+ M:Avalonia.Platform.Screen.set_WorkingArea(Avalonia.PixelRect)
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
@@ -277,6 +2119,36 @@
baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+
+ CP0002
+ M:Avalonia.Dialogs.ManagedFileDialogExtensions.ShowManagedAsync(Avalonia.Controls.OpenFileDialog,Avalonia.Controls.Window,Avalonia.Dialogs.ManagedFileDialogOptions)
+ baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+
+
+ CP0002
+ M:Avalonia.Dialogs.ManagedFileDialogExtensions.ShowManagedAsync``1(Avalonia.Controls.OpenFileDialog,Avalonia.Controls.Window,Avalonia.Dialogs.ManagedFileDialogOptions)
+ baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+
+
+ CP0002
+ M:Avalonia.Data.Binding.#ctor(System.String,Avalonia.Data.BindingMode)
+ baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll
+ current/Avalonia/lib/net8.0/Avalonia.Markup.dll
+
+
+ CP0002
+ M:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension.#ctor(System.String,Avalonia.Data.BindingMode)
+ baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll
+ current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll
+
+
+ CP0002
+ M:Avalonia.Markup.Xaml.XamlLoadException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
+ baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll
+ current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll
+
CP0002
F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache
@@ -302,32 +2174,98 @@
current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
- CP0002
- 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.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.IDrawingContextImplWithEffects.PopEffect
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect)
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0006
+ 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.Platform.IDrawingContextImpl.PopTextOptions
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0006
+ 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.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.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.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.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.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.IGeometryContext.QuadraticBezierTo(Avalonia.Point,Avalonia.Point,System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
- CP0002
- M:Avalonia.Platform.IDrawingContextImplWithEffects.PopEffect
- baseline/netstandard2.0/Avalonia.Base.dll
- target/netstandard2.0/Avalonia.Base.dll
+ CP0006
+ M:Avalonia.Platform.IPlatformRenderInterfaceContext.CreateOffscreenRenderTarget(Avalonia.PixelSize,Avalonia.Vector,System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
- CP0002
- M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect)
- baseline/netstandard2.0/Avalonia.Base.dll
- target/netstandard2.0/Avalonia.Base.dll
+ CP0006
+ P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
CP0006
- M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(System.Nullable{Avalonia.Rect},Avalonia.Media.IEffect)
+ P:Avalonia.Platform.IPlatformRenderInterfaceContext.MaxOffscreenRenderTargetPixelSize
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.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.Typeface@)
+ P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
@@ -391,6 +2329,12 @@
baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+
+ CP0006
+ 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
M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)
@@ -415,6 +2359,18 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0006
+ M:Avalonia.Platform.IDrawingContextImpl.PopTextOptions
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0006
+ 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.Platform.IDrawingContextImplWithEffects.PushEffect(System.Nullable{Avalonia.Rect},Avalonia.Media.IEffect)
@@ -427,6 +2383,36 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0006
+ 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.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.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.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.IPlatformRenderInterfaceContext.CreateOffscreenRenderTarget(Avalonia.PixelSize,Avalonia.Vector,System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0006
M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)
@@ -439,6 +2425,24 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0006
+ P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0006
+ P:Avalonia.Platform.IPlatformRenderInterfaceContext.MaxOffscreenRenderTargetPixelSize
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0006
+ P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat
+ 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)
@@ -517,6 +2521,282 @@
baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+
+ CP0007
+ T:Avalonia.Controls.ResourcesChangedEventArgs
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0007
+ T:Avalonia.Controls.ResourcesChangedEventArgs
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Input.DataObject
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Media.ImmediateDrawingContext
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Media.StreamGeometryContext
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPlatformGraphicsContext
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPlatformGraphicsWithFeatures
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPlatformRenderInterfaceContext
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IWriteableBitmapImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Application
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPopupImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.ITopLevelImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IWindowBaseImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IWindowImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Metal.IMetalDevice
+ baseline/Avalonia/lib/net10.0/Avalonia.Metal.dll
+ current/Avalonia/lib/net10.0/Avalonia.Metal.dll
+
+
+ CP0008
+ T:Avalonia.OpenGL.Egl.EglContext
+ baseline/Avalonia/lib/net10.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net10.0/Avalonia.OpenGL.dll
+
+
+ CP0008
+ T:Avalonia.OpenGL.IGlContext
+ baseline/Avalonia/lib/net10.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net10.0/Avalonia.OpenGL.dll
+
+
+ CP0008
+ T:Avalonia.Vulkan.IVulkanDevice
+ baseline/Avalonia/lib/net10.0/Avalonia.Vulkan.dll
+ current/Avalonia/lib/net10.0/Avalonia.Vulkan.dll
+
+
+ CP0008
+ T:Avalonia.Vulkan.IVulkanPlatformGraphicsContext
+ baseline/Avalonia/lib/net10.0/Avalonia.Vulkan.dll
+ current/Avalonia/lib/net10.0/Avalonia.Vulkan.dll
+
+
+ CP0008
+ T:Avalonia.Input.DataObject
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Media.ImmediateDrawingContext
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.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
+
+
+ CP0008
+ T:Avalonia.Platform.IPlatformGraphicsContext
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPlatformGraphicsWithFeatures
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPlatformRenderInterfaceContext
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IWriteableBitmapImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Application
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IPopupImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.ITopLevelImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IWindowBaseImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Platform.IWindowImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Metal.IMetalDevice
+ baseline/Avalonia/lib/net8.0/Avalonia.Metal.dll
+ current/Avalonia/lib/net8.0/Avalonia.Metal.dll
+
+
+ CP0008
+ T:Avalonia.OpenGL.Egl.EglContext
+ baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+
+
+ CP0008
+ T:Avalonia.OpenGL.IGlContext
+ baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+
+
+ CP0008
+ T:Avalonia.Vulkan.IVulkanDevice
+ baseline/Avalonia/lib/net8.0/Avalonia.Vulkan.dll
+ current/Avalonia/lib/net8.0/Avalonia.Vulkan.dll
+
+
+ CP0008
+ T:Avalonia.Vulkan.IVulkanPlatformGraphicsContext
+ baseline/Avalonia/lib/net8.0/Avalonia.Vulkan.dll
+ current/Avalonia/lib/net8.0/Avalonia.Vulkan.dll
+
+
+ CP0009
+ T:Avalonia.Controls.ResourcesChangedEventArgs
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0009
+ T:Avalonia.Input.DataObject
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0009
+ T:Avalonia.Platform.Screen
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0009
+ T:Avalonia.Controls.ResourcesChangedEventArgs
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0009
+ T:Avalonia.Input.DataObject
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0009
+ T:Avalonia.Platform.Screen
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0012
M:Avalonia.Media.Fonts.FontCollectionBase.get_Count
@@ -547,6 +2827,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.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
+
CP0012
M:Avalonia.Media.Fonts.FontCollectionBase.get_Count
@@ -607,6 +2893,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.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
+
CP0012
M:Avalonia.Media.Fonts.FontCollectionBase.get_Count
@@ -637,4 +2929,4 @@
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/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml
index e1e037ed3e..8a91e8fd68 100644
--- a/azure-pipelines-integrationtests.yml
+++ b/azure-pipelines-integrationtests.yml
@@ -43,7 +43,7 @@ jobs:
inputs:
command: 'test'
projects: 'tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj'
- arguments: '-l "console;verbosity=detailed"'
+ arguments: '--no-progress'
- script: |
pkill IntegrationTestApp
@@ -94,6 +94,7 @@ jobs:
inputs:
command: 'run'
projects: 'tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj'
+ arguments: '--no-progress'
- task: VSTest@2
displayName: 'Run Appium Integration Tests'
diff --git a/build/AnalyzerProject.targets b/build/AnalyzerProject.targets
index f067ec0418..4a95cb306e 100644
--- a/build/AnalyzerProject.targets
+++ b/build/AnalyzerProject.targets
@@ -3,12 +3,12 @@
true
true
+ true
+ $(NoWarn);RS2008
-
-
-
+
diff --git a/build/DevAnalyzers.props b/build/DevAnalyzers.props
index fd416e3291..ae5e7a6c42 100644
--- a/build/DevAnalyzers.props
+++ b/build/DevAnalyzers.props
@@ -1,12 +1,20 @@
-
-
+
+
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index 8f457eb46e..c7a3d753f2 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/ImageSharp.props b/build/ImageSharp.props
index 4f04171a9a..cf401630b0 100644
--- a/build/ImageSharp.props
+++ b/build/ImageSharp.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/Microsoft.Reactive.Testing.props b/build/Microsoft.Reactive.Testing.props
index a0ba2163bb..c39c72df77 100644
--- a/build/Microsoft.Reactive.Testing.props
+++ b/build/Microsoft.Reactive.Testing.props
@@ -1,5 +1,5 @@
-
+
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/Rx.props b/build/Rx.props
index 73b18c6e0b..462428c286 100644
--- a/build/Rx.props
+++ b/build/Rx.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/SampleApp.props b/build/SampleApp.props
index 8ecbe902ab..3f44553d2d 100644
--- a/build/SampleApp.props
+++ b/build/SampleApp.props
@@ -13,6 +13,10 @@
+
+
+
+
-
-
-
+
+
+
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 4b00869d5b..5c63ed69db 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -1,18 +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/Avalonia.Controls.DataGrid b/external/Avalonia.Controls.DataGrid
deleted file mode 160000
index 85a0b32ef6..0000000000
--- a/external/Avalonia.Controls.DataGrid
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 85a0b32ef6d963c1d67619ca3e2f6da0bc43ac9a
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/global.json b/global.json
index 60d847ac0c..3773c7d736 100644
--- a/global.json
+++ b/global.json
@@ -1,8 +1,11 @@
{
"sdk": {
- "version": "10.0.100",
+ "version": "10.0.101",
"rollForward": "latestFeature"
},
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ },
"msbuild-sdks": {
"Microsoft.Build.Traversal": "4.1.0"
}
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/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm
index d4942afced..600dca865f 100644
--- a/native/Avalonia.Native/src/OSX/AvnView.mm
+++ b/native/Avalonia.Native/src/OSX/AvnView.mm
@@ -42,7 +42,8 @@
- (void) updateRenderTarget
{
if(_currentRenderTarget) {
- [_currentRenderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])];
+ AvnPixelSize size { MAX(_lastPixelSize.Width, 1), MAX(_lastPixelSize.Height, 1) };
+ [_currentRenderTarget resize:size withScale:static_cast([[self window] backingScaleFactor])];
[self setNeedsDisplayInRect:[self frame]];
}
}
@@ -854,9 +855,10 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id )info
{
- auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
- auto avnPoint = ToAvnPoint(localPoint);
- auto point = [self translateLocalPoint:avnPoint];
+ NSPoint eventLocation = [info draggingLocation];
+ auto viewLocation = [self convertPoint:NSMakePoint(0, 0) toView:nil];
+ auto localPoint = NSMakePoint(eventLocation.x - viewLocation.x, viewLocation.y - eventLocation.y);
+ auto point = ToAvnPoint(localPoint);
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
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/ApiDiffHelper.cs b/nukebuild/ApiDiffHelper.cs
index 4a51630557..d16495969e 100644
--- a/nukebuild/ApiDiffHelper.cs
+++ b/nukebuild/ApiDiffHelper.cs
@@ -304,44 +304,95 @@ public static class ApiDiffHelper
{
using var packageReader = new PackageArchiveReader(currentArchive);
packageId = packageReader.NuspecReader.GetId();
+
+ baselineFolderPath = outputFolderPath / "baseline" / packageId;
+ Directory.CreateDirectory(baselineFolderPath);
+
currentFolderPath = outputFolderPath / "current" / packageId;
+ Directory.CreateDirectory(currentFolderPath);
+
currentFolderNames = ExtractDiffableAssembliesFromPackage(currentArchive, currentFolderPath);
}
- // Download baseline package
- memoryStream.Position = 0L;
- memoryStream.SetLength(0L);
- await DownloadBaselinePackageAsync(memoryStream, downloadContext, packageId, baselineVersion);
- memoryStream.Position = 0L;
+ var packageExists = await downloadContext.FindPackageByIdResource.DoesPackageExistAsync(
+ packageId,
+ baselineVersion,
+ downloadContext.CacheContext,
+ NullLogger.Instance,
+ CancellationToken.None);
- // Extract baseline package
- using (var baselineArchive = new ZipArchive(memoryStream, ZipArchiveMode.Read, leaveOpen: true))
+ if (packageExists)
{
- baselineFolderPath = outputFolderPath / "baseline" / packageId;
+ // Download baseline package
+ memoryStream.Position = 0L;
+ memoryStream.SetLength(0L);
+ await DownloadBaselinePackageAsync(memoryStream, downloadContext, packageId, baselineVersion);
+ memoryStream.Position = 0L;
+
+ // Extract baseline package
+ using var baselineArchive = new ZipArchive(memoryStream, ZipArchiveMode.Read, leaveOpen: true);
baselineFolderNames = ExtractDiffableAssembliesFromPackage(baselineArchive, baselineFolderPath);
}
+ else
+ {
+ Information("Baseline package {Id} {Version} does not exist. Assuming new package.", packageId, baselineVersion);
+ baselineFolderNames = [];
+ }
if (currentFolderNames.Count == 0 && baselineFolderNames.Count == 0)
continue;
var frameworkDiffs = new List();
+ // Match frameworks
foreach (var (framework, currentFolderName) in currentFolderNames)
{
- // Ignore new frameworks that didn't exist in the baseline package. Empty folders make the ApiDiff tool crash.
if (!baselineFolderNames.TryGetValue(framework, out var baselineFolderName))
- continue;
+ baselineFolderName = currentFolderName;
- frameworkDiffs.Add(new FrameworkDiffInfo(
+ var frameworkDiff = new FrameworkDiffInfo(
framework,
baselineFolderPath / FolderLib / baselineFolderName,
- currentFolderPath / FolderLib / currentFolderName));
+ currentFolderPath / FolderLib / currentFolderName);
+
+ EnsureAssemblies(frameworkDiff);
+
+ frameworkDiffs.Add(frameworkDiff);
}
packageDiffs.Add(new PackageDiffInfo(packageId, [..frameworkDiffs]));
}
return new GlobalDiffInfo(baselineVersion, currentVersion, packageDiffs.DrainToImmutable());
+
+ // Ensure that both sides of a framework diff have matching assemblies.
+ // For any missing, generate an empty assembly to diff against.
+ // (The API diff tool supports added and removed assemblies in theory but actually throws if one side doesn't have any.)
+ static void EnsureAssemblies(FrameworkDiffInfo frameworkDiff)
+ {
+ Directory.CreateDirectory(frameworkDiff.BaselineFolderPath);
+ Directory.CreateDirectory(frameworkDiff.CurrentFolderPath);
+
+ var baselineFileNames = GetFileNames(frameworkDiff.BaselineFolderPath);
+ var currentFileNames = GetFileNames(frameworkDiff.CurrentFolderPath);
+
+ GenerateMissingAssemblies(currentFileNames.Except(baselineFileNames), frameworkDiff.BaselineFolderPath);
+ GenerateMissingAssemblies(baselineFileNames.Except(currentFileNames), frameworkDiff.CurrentFolderPath);
+
+ static string[] GetFileNames(string folderPath)
+ => Directory.EnumerateFiles(folderPath, "*.dll").Select(Path.GetFileName)!.ToArray();
+
+ void GenerateMissingAssemblies(IEnumerable missingFileNames, string folderPath)
+ {
+ foreach (var missingFileName in missingFileNames)
+ {
+ GenerateEmptyAssembly(
+ Path.GetFileNameWithoutExtension(missingFileName),
+ frameworkDiff.Framework.GetShortFolderName(),
+ Path.Join(folderPath, missingFileName));
+ }
+ }
+ }
}
static async Task CreateNuGetDownloadContextAsync()
@@ -453,6 +504,44 @@ public static class ApiDiffHelper
value;
}
+ static void GenerateEmptyAssembly(string name, string framework, string outputFilePath)
+ {
+ var projectContents =
+ $"""
+
+
+ {framework}
+ Release
+ None
+
+
+ """;
+
+ var tempDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ var projectFilePath = Path.Join(tempDirPath, $"{name}.csproj");
+
+ Directory.CreateDirectory(tempDirPath);
+
+ try
+ {
+ File.WriteAllText(projectFilePath, projectContents);
+
+ using var process = ProcessTasks.StartProcess(
+ "dotnet",
+ $"build \"{projectFilePath}\" --output \"{tempDirPath}\"",
+ tempDirPath);
+
+ process.AssertZeroExitCode();
+
+ File.Copy(Path.Join(tempDirPath, $"{name}.dll"), outputFilePath);
+ }
+ finally
+ {
+ if (Directory.Exists(tempDirPath))
+ Directory.Delete(tempDirPath, true);
+ }
+ }
+
public sealed class GlobalDiffInfo(
NuGetVersion baselineVersion,
NuGetVersion currentVersion,
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index d6d636aac1..41c5cbe0cf 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -36,13 +36,13 @@ partial class Build : NukeBuild
[NuGetPackage("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net8.0")]
Tool ApiCompatTool;
-
+
[NuGetPackage("Microsoft.DotNet.ApiDiff.Tool", "Microsoft.DotNet.ApiDiff.Tool.dll", Framework = "net8.0")]
Tool ApiDiffTool;
[NuGetPackage("dotnet-ilrepack", "ILRepackTool.dll", Framework = "net8.0")]
Tool IlRepackTool;
-
+
protected override void OnBuildInitialized()
{
Parameters = new BuildParameters(this, ScheduledTargets.Contains(BuildToNuGetCache));
@@ -57,6 +57,7 @@ partial class Build : NukeBuild
Information("Repository Name: " + Parameters.RepositoryName);
Information("Repository Branch: " + Parameters.RepositoryBranch);
}
+
Information("Configuration: " + Parameters.Configuration);
Information("IsLocalBuild: " + Parameters.IsLocalBuild);
Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix);
@@ -73,8 +74,9 @@ partial class Build : NukeBuild
void ExecWait(string preamble, string command, string args)
{
Console.WriteLine(preamble);
- Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit();
+ Process.Start(new ProcessStartInfo(command, args) { UseShellExecute = false }).WaitForExit();
}
+
ExecWait("dotnet version:", "dotnet", "--info");
ExecWait("dotnet workloads:", "dotnet", "workload list");
Information("Processor count: " + Environment.ProcessorCount);
@@ -99,6 +101,7 @@ partial class Build : NukeBuild
.AddProperty("SkipBuildingTests", "True");
return c;
}
+
DotNetBuildSettings ApplySetting(DotNetBuildSettings c, Configure configurator = null) =>
ApplySettingCore(c).Build.Apply(configurator);
@@ -108,6 +111,9 @@ partial class Build : NukeBuild
DotNetTestSettings ApplySetting(DotNetTestSettings c, Configure configurator = null) =>
ApplySettingCore(c).Test.Apply(configurator);
+ DotNetRunSettings ApplySetting(DotNetRunSettings c, Configure configurator = null) =>
+ ApplySettingCore(c).Run.Apply(configurator);
+
Target Clean => _ => _.Executes(() =>
{
foreach (var buildDir in Parameters.BuildDirs)
@@ -129,6 +135,17 @@ partial class Build : NukeBuild
}
});
+ // Ensure that Bun.Official.Tool is downloaded at least once on CI to work around https://github.com/dotnet/sdk/issues/51831
+ Target InitDnx => _ => _
+ .Executes(() =>
+ {
+ var process = ProcessTasks.StartProcess(
+ "dnx",
+ "Bun.Unofficial.Tool --yes -- install",
+ $"{RootDirectory}/src/Browser/Avalonia.Browser/webapp");
+ process.AssertZeroExitCode();
+ });
+
Target CompileNative => _ => _
.DependsOn(Clean)
.DependsOn(GenerateCppHeaders)
@@ -141,7 +158,7 @@ partial class Build : NukeBuild
});
Target Compile => _ => _
- .DependsOn(Clean, CompileNative)
+ .DependsOn(Clean, CompileNative, InitDnx)
.Executes(() =>
{
DotNetBuild(c => ApplySetting(c)
@@ -168,7 +185,10 @@ partial class Build : NukeBuild
{
RunCoreTest(projectName, (project, tfm) =>
{
- DotNetTest(c => ApplySetting(c, project,tfm));
+ // NOTE: Nuke DotNetTest doesn't support Microsoft.Testing.Platform yet.
+ // Issue: https://github.com/nuke-build/nuke/issues/1584.
+ // However, we can easily use DotNetRun instead since MTP projects are executables.
+ DotNetRun(c => ApplySetting(c, project, tfm));
});
}
@@ -222,15 +242,17 @@ partial class Build : NukeBuild
}
}
- DotNetTestSettings ApplySetting(DotNetTestSettings settings, string project, string tfm) =>
+ DotNetRunSettings ApplySetting(DotNetRunSettings settings, string project, string tfm) =>
ApplySetting(settings)
.SetProjectFile(project)
.SetFramework(tfm)
.EnableNoBuild()
.EnableNoRestore()
+ // Disable progress output (works like terminal logger which isn't so nice in CI).
+ // See https://github.com/microsoft/testfx/issues/7056
+ .AddApplicationArguments("--no-progress")
.When(_ => Parameters.PublishTestResults, _ => _
- .SetLoggers("trx")
- .SetResultsDirectory(Parameters.TestResultsRoot));
+ .AddApplicationArguments("--report-trx", "--results-directory", Parameters.TestResultsRoot));
Target RunHtmlPreviewerTests => _ => _
.OnlyWhenStatic(() => !(Parameters.SkipTests))
@@ -308,7 +330,7 @@ partial class Build : NukeBuild
BuildTasksPatcher.PatchBuildTasksInPackage(Parameters.NugetIntermediateRoot / "Avalonia.Build.Tasks." +
Parameters.Version + ".nupkg",
IlRepackTool);
- var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
+ var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.json");
Parameters.NugetRoot.CreateOrCleanDirectory();
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
new NumergeNukeLogger()))
@@ -322,11 +344,14 @@ partial class Build : NukeBuild
.DependsOn(CreateNugetPackages)
.Executes(async () =>
{
+ var apiDiffPath = Parameters.ArtifactsDir / "api-diff";
+ apiDiffPath.DeleteDirectory();
+
GlobalDiff = await ApiDiffHelper.DownloadAndExtractPackagesAsync(
Directory.EnumerateFiles(Parameters.NugetRoot, "*.nupkg").Select(path => (AbsolutePath)path),
NuGetVersion.Parse(Parameters.Version),
Parameters.IsReleaseBranch,
- Parameters.ArtifactsDir / "api-diff" / "assemblies",
+ apiDiffPath / "assemblies",
Parameters.ForceApiValidationBaseline is { } forcedBaseline ? NuGetVersion.Parse(forcedBaseline) : null);
});
diff --git a/nukebuild/DotNetConfigHelper.cs b/nukebuild/DotNetConfigHelper.cs
index 9d43261616..b5315b3f5a 100644
--- a/nukebuild/DotNetConfigHelper.cs
+++ b/nukebuild/DotNetConfigHelper.cs
@@ -7,6 +7,7 @@ public class DotNetConfigHelper
public DotNetBuildSettings Build;
public DotNetPackSettings Pack;
public DotNetTestSettings Test;
+ public DotNetRunSettings Run;
public DotNetConfigHelper(DotNetBuildSettings s)
{
@@ -23,6 +24,11 @@ public class DotNetConfigHelper
Test = s;
}
+ public DotNetConfigHelper(DotNetRunSettings s)
+ {
+ Run = s;
+ }
+
public DotNetConfigHelper AddProperty(string key, bool value) =>
AddProperty(key, value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
public DotNetConfigHelper AddProperty(string key, string value)
@@ -30,6 +36,7 @@ public class DotNetConfigHelper
Build = Build?.AddProperty(key, value);
Pack = Pack?.AddProperty(key, value);
Test = Test?.AddProperty(key, value);
+ Run = Run?.AddProperty(key, value);
return this;
}
@@ -39,6 +46,7 @@ public class DotNetConfigHelper
Build = Build?.SetConfiguration(configuration);
Pack = Pack?.SetConfiguration(configuration);
Test = Test?.SetConfiguration(configuration);
+ Run = Run?.SetConfiguration(configuration);
return this;
}
@@ -47,10 +55,12 @@ public class DotNetConfigHelper
Build = Build?.SetVerbosity(verbosity);
Pack = Pack?.SetVerbosity(verbosity);
Test = Test?.SetVerbosity(verbosity);
+ Run = Run?.SetVerbosity(verbosity);
return this;
}
public static implicit operator DotNetConfigHelper(DotNetBuildSettings s) => new DotNetConfigHelper(s);
public static implicit operator DotNetConfigHelper(DotNetPackSettings s) => new DotNetConfigHelper(s);
public static implicit operator DotNetConfigHelper(DotNetTestSettings s) => new DotNetConfigHelper(s);
+ public static implicit operator DotNetConfigHelper(DotNetRunSettings s) => new DotNetConfigHelper(s);
}
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index b57414b32e..c3ee103e73 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -4,23 +4,21 @@
false
False
- $(NoWarn);CS0649;CS0169;SYSLIB0011
+ $(NoWarn);CS0649;CA1847
1
$(AvsCurrentTargetFramework)
-
- true
-
+
-
-
-
+
+
+
-
+
diff --git a/nukebuild/numerge.config b/nukebuild/numerge.json
similarity index 70%
rename from nukebuild/numerge.config
rename to nukebuild/numerge.json
index 8af0af7ff0..392348a044 100644
--- a/nukebuild/numerge.config
+++ b/nukebuild/numerge.json
@@ -18,7 +18,17 @@
"DoNotMergeDependencies": true
},
{
- "Id": "Avalonia.Analyzers",
+ "Id": "Avalonia.Analyzers.CSharp",
+ "IgnoreMissingFrameworkBinaries": true,
+ "DoNotMergeDependencies": true
+ },
+ {
+ "Id": "Avalonia.Analyzers.VisualBasic",
+ "IgnoreMissingFrameworkBinaries": true,
+ "DoNotMergeDependencies": true
+ },
+ {
+ "Id": "Avalonia.Analyzers.CodeFixes.CSharp",
"IgnoreMissingFrameworkBinaries": true,
"DoNotMergeDependencies": true
}
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 1cd1245d3f..e59e72f13b 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -9,11 +9,19 @@
-
-
+
+
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/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 8fe77a095d..d3750935f9 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -134,6 +134,8 @@
false
false
false
+ true
+ false
@@ -162,6 +164,7 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
+ CreateSourceInfo="$(AvaloniaXamlCreateSourceInfo)"
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
AnalyzerConfigFiles="@(EditorConfigFiles)"/>
diff --git a/packages/Avalonia/AvaloniaRules.Project.xml b/packages/Avalonia/AvaloniaRules.Project.xml
index b69ea6de17..0a5c1b8243 100644
--- a/packages/Avalonia/AvaloniaRules.Project.xml
+++ b/packages/Avalonia/AvaloniaRules.Project.xml
@@ -31,6 +31,11 @@
Description="Allow debug XAML compilation"
Category="Debug" />
+
+
WinExe
$(AvsCurrentTargetFramework)
- enable
app.manifest
-
diff --git a/samples/AppWithoutLifetime/MainWindow.axaml.cs b/samples/AppWithoutLifetime/MainWindow.axaml.cs
index df3ed3cea0..a92bd31600 100644
--- a/samples/AppWithoutLifetime/MainWindow.axaml.cs
+++ b/samples/AppWithoutLifetime/MainWindow.axaml.cs
@@ -1,4 +1,3 @@
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
@@ -17,13 +16,6 @@ public partial class MainWindow : Window
AvaloniaXamlLoader.Load(this);
}
- ///
- protected override void OnLoaded(RoutedEventArgs e)
- {
- this.AttachDevTools();
- base.OnLoaded(e);
- }
-
public void Open(object sender, RoutedEventArgs e)
{
new Sub().Show(this);
diff --git a/samples/AppWithoutLifetime/Program.cs b/samples/AppWithoutLifetime/Program.cs
index 9404b9f01a..60ab32824e 100644
--- a/samples/AppWithoutLifetime/Program.cs
+++ b/samples/AppWithoutLifetime/Program.cs
@@ -24,5 +24,6 @@ class Program
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
+ .WithDeveloperTools()
.LogToTrace();
}
diff --git a/samples/AppWithoutLifetime/Sub.axaml.cs b/samples/AppWithoutLifetime/Sub.axaml.cs
index 3a7ce787bc..e0d07fb338 100644
--- a/samples/AppWithoutLifetime/Sub.axaml.cs
+++ b/samples/AppWithoutLifetime/Sub.axaml.cs
@@ -1,6 +1,4 @@
-using Avalonia;
using Avalonia.Controls;
-using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace AppWithoutLifetime;
@@ -16,11 +14,4 @@ public partial class Sub : Window
{
AvaloniaXamlLoader.Load(this);
}
-
- ///
- protected override void OnLoaded(RoutedEventArgs e)
- {
- this.AttachDevTools();
- base.OnLoaded(e);
- }
}
diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs
index 8a5364c70b..e76f61ac63 100644
--- a/samples/BindingDemo/App.xaml.cs
+++ b/samples/BindingDemo/App.xaml.cs
@@ -24,6 +24,7 @@ namespace BindingDemo
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
+ .WithDeveloperTools()
.LogToTrace();
}
}
diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj
index 33c6aa1440..b12e350814 100644
--- a/samples/BindingDemo/BindingDemo.csproj
+++ b/samples/BindingDemo/BindingDemo.csproj
@@ -5,7 +5,6 @@
-
diff --git a/samples/BindingDemo/GenericMarkupExtension.cs b/samples/BindingDemo/GenericMarkupExtension.cs
index aed0700cb8..be7d82e0d5 100644
--- a/samples/BindingDemo/GenericMarkupExtension.cs
+++ b/samples/BindingDemo/GenericMarkupExtension.cs
@@ -5,7 +5,7 @@ namespace BindingDemo;
internal class GenericMarkupExtension : MarkupExtension
{
- public T Value { get; set; }
+ public T? Value { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml
index 9d68c8da8a..3ff80069f4 100644
--- a/samples/BindingDemo/MainWindow.xaml
+++ b/samples/BindingDemo/MainWindow.xaml
@@ -1,7 +1,7 @@
-
+
-
-
-
-
+
+
+
+
-
+
-
+
!BooleanString
!!BooleanString
@@ -43,24 +43,24 @@
-
+
-
-
-
-
+
-
+
@@ -91,19 +91,19 @@
-
+
-
+
-
-
+
+
-
+
-
-
+
+
diff --git a/samples/BindingDemo/MainWindow.xaml.cs b/samples/BindingDemo/MainWindow.xaml.cs
index 36d4feb108..6a7c1e877d 100644
--- a/samples/BindingDemo/MainWindow.xaml.cs
+++ b/samples/BindingDemo/MainWindow.xaml.cs
@@ -1,5 +1,4 @@
using BindingDemo.ViewModels;
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
@@ -12,7 +11,6 @@ namespace BindingDemo
Resources["SharedItem"] = new MainWindowViewModel.TestItem() { Value = "shared" };
this.InitializeComponent();
this.DataContext = new MainWindowViewModel();
- this.AttachDevTools();
}
private void InitializeComponent()
diff --git a/samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs b/samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs
index 391d1791d3..a51139f8ac 100644
--- a/samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs
+++ b/samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs
@@ -6,7 +6,7 @@ namespace BindingDemo.ViewModels
{
[Phone]
[MaxLength(10)]
- public string PhoneNumber { get; set; }
+ public string? PhoneNumber { get; set; }
[Range(0, 9)]
public int LessThan10 { get; set; }
diff --git a/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs b/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs
index 9ae8d9558f..7273da38c8 100644
--- a/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs
+++ b/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs
@@ -9,7 +9,7 @@ namespace BindingDemo.ViewModels
{
private int _maximum = 10;
private int _value;
- private string _valueError;
+ private string? _valueError;
public IndeiErrorViewModel()
{
@@ -34,16 +34,16 @@ namespace BindingDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref _value, value); }
}
- public event EventHandler ErrorsChanged;
+ public event EventHandler? ErrorsChanged;
- public IEnumerable GetErrors(string propertyName)
+ public IEnumerable GetErrors(string? propertyName)
{
switch (propertyName)
{
case nameof(Value):
return new[] { _valueError };
default:
- return null;
+ return Array.Empty();
}
}
diff --git a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs
index eb1a007695..3fce67a6f6 100644
--- a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs
+++ b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs
@@ -16,10 +16,10 @@ namespace BindingDemo.ViewModels
{
private string _booleanString = "True";
private double _doubleValue = 5.0;
- private string _stringValue = "Simple Binding";
+ private string? _stringValue = "Simple Binding";
private bool _booleanFlag = false;
- private string _currentTime;
- private NestedCommandViewModel _nested;
+ private string? _currentTime;
+ private NestedCommandViewModel? _nested;
public MainWindowViewModel()
{
@@ -74,7 +74,7 @@ namespace BindingDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref _doubleValue, value); }
}
- public string StringValue
+ public string? StringValue
{
get { return _stringValue; }
set { this.RaiseAndSetIfChanged(ref _stringValue, value); }
@@ -86,7 +86,7 @@ namespace BindingDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref _booleanFlag, value); }
}
- public string CurrentTime
+ public string? CurrentTime
{
get { return _currentTime; }
private set { this.RaiseAndSetIfChanged(ref _currentTime, value); }
@@ -99,7 +99,7 @@ namespace BindingDemo.ViewModels
public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel();
public IndeiErrorViewModel IndeiDataValidation { get; } = new IndeiErrorViewModel();
- public NestedCommandViewModel NestedModel
+ public NestedCommandViewModel? NestedModel
{
get { return _nested; }
private set { this.RaiseAndSetIfChanged(ref _nested, value); }
@@ -119,16 +119,16 @@ namespace BindingDemo.ViewModels
// Nested class, jsut so we can test it in XAML
public class TestItem : ViewModelBase
{
- private T _value;
- private string _detail;
+ private T? _value;
+ private string? _detail;
- public T Value
+ public T? Value
{
get { return _value; }
set { this.RaiseAndSetIfChanged(ref this._value, value); }
}
- public string Detail
+ public string? Detail
{
get { return _detail; }
set { this.RaiseAndSetIfChanged(ref this._detail, value); }
diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index f60ba69395..c2fe16970a 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -3,7 +3,6 @@
$(AvsCurrentAndroidTargetFramework)
$(AvsMinSupportedAndroidVersion)
Exe
- enable
com.Avalonia.ControlCatalog
1
1.0
@@ -26,7 +25,7 @@
-
+
diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs
index 4963d12997..b9f2d16168 100644
--- a/samples/ControlCatalog.Android/MainActivity.cs
+++ b/samples/ControlCatalog.Android/MainActivity.cs
@@ -10,7 +10,7 @@ using static Android.Content.Intent;
namespace ControlCatalog.Android
{
- [Activity(Name = "com.Avalonia.ControlCatalog.MainActivity", Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, Exported = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
+ [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, Exported = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
// CategoryLeanbackLauncher is required for Android TV.
[IntentFilter(new[] { ActionView }, Categories = new[] { CategoryDefault, CategoryLeanbackLauncher })]
public class MainActivity : AvaloniaMainActivity
diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
index 9398987837..c951862ad2 100644
--- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
+++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
@@ -20,7 +20,6 @@
-
diff --git a/samples/ControlCatalog.Desktop/NativeControls/Gtk/EmbedSample.Gtk.cs b/samples/ControlCatalog.Desktop/NativeControls/Gtk/EmbedSample.Gtk.cs
index c42ac73b88..523503cc38 100644
--- a/samples/ControlCatalog.Desktop/NativeControls/Gtk/EmbedSample.Gtk.cs
+++ b/samples/ControlCatalog.Desktop/NativeControls/Gtk/EmbedSample.Gtk.cs
@@ -9,7 +9,7 @@ namespace ControlCatalog.Desktop;
public class EmbedSampleGtk : INativeDemoControl
{
- private Process _mplayer;
+ private Process? _mplayer;
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func createDefault)
{
diff --git a/samples/ControlCatalog.Desktop/NativeControls/Mac/MacHelper.cs b/samples/ControlCatalog.Desktop/NativeControls/Mac/MacHelper.cs
index 154878e4ff..01325a5fa9 100644
--- a/samples/ControlCatalog.Desktop/NativeControls/Mac/MacHelper.cs
+++ b/samples/ControlCatalog.Desktop/NativeControls/Mac/MacHelper.cs
@@ -20,7 +20,7 @@ internal class MacHelper
internal class MacOSViewHandle : INativeControlHostDestroyableControlHandle
{
- private NSView _view;
+ private NSView? _view;
public MacOSViewHandle(NSView view)
{
@@ -32,7 +32,7 @@ internal class MacOSViewHandle : INativeControlHostDestroyableControlHandle
public void Destroy()
{
- _view.Dispose();
+ _view?.Dispose();
_view = null;
}
}
diff --git a/samples/ControlCatalog.Desktop/NativeControls/Win/WinApi.cs b/samples/ControlCatalog.Desktop/NativeControls/Win/WinApi.cs
index 1bb3879ac3..514f704b30 100644
--- a/samples/ControlCatalog.Desktop/NativeControls/Win/WinApi.cs
+++ b/samples/ControlCatalog.Desktop/NativeControls/Win/WinApi.cs
@@ -44,7 +44,7 @@ internal unsafe class WinApi
[DllImport("kernel32.dll")]
- public static extern IntPtr GetModuleHandle(string lpModuleName);
+ public static extern IntPtr GetModuleHandle(string? lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs
index 707ead5f1d..819072db18 100644
--- a/samples/ControlCatalog.Desktop/Program.cs
+++ b/samples/ControlCatalog.Desktop/Program.cs
@@ -7,10 +7,11 @@ using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Fonts.Inter;
using Avalonia.Headless;
+using Avalonia.LinuxFramebuffer;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.LogicalTree;
+using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Vulkan;
@@ -20,16 +21,9 @@ namespace ControlCatalog.Desktop
{
static class Program
{
- private static bool s_useFramebuffer;
-
[STAThread]
static int Main(string[] args)
{
- if (args.Contains("--fbdev"))
- {
- s_useFramebuffer = true;
- }
-
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");
@@ -46,12 +40,27 @@ namespace ControlCatalog.Desktop
double GetScaling()
{
var idx = Array.IndexOf(args, "--scaling");
- if (idx != 0 && args.Length > idx + 1 &&
+ if (idx >= 0 && args.Length > idx + 1 &&
double.TryParse(args[idx + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out var scaling))
return scaling;
return 1;
}
- if (s_useFramebuffer)
+ SurfaceOrientation GetOrientation()
+ {
+ var idx = Array.IndexOf(args, "--orientation");
+ if (idx >= 0 && args.Length > idx + 1 &&
+ Enum.TryParse(args[idx + 1], true, out var orientation))
+ return orientation;
+ return SurfaceOrientation.Rotation0;
+ }
+ string? GetCard()
+ {
+ var idx = Array.IndexOf(args, "--card");
+ if (idx >= 0 && args.Length > idx + 1)
+ return args[idx + 1];
+ return null;
+ }
+ if (args.Contains("--fbdev"))
{
SilenceConsole();
return builder.StartLinuxFbDev(args, new FbDevOutputOptions()
@@ -74,12 +83,12 @@ namespace ControlCatalog.Desktop
{
DispatcherTimer.RunOnce(async () =>
{
- var window = ((IClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime)
- .MainWindow;
+ var window = ((IClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!)
+ .MainWindow!;
var tc = window.GetLogicalDescendants().OfType().First();
foreach (var page in tc.Items.Cast().ToList())
{
- if (page.Header.ToString() == "DatePicker" || page.Header.ToString() == "TreeView")
+ if (page.Header?.ToString() is "DatePicker" or "TreeView")
continue;
Console.WriteLine("Selecting " + page.Header);
tc.SelectedItem = page;
@@ -109,13 +118,17 @@ namespace ControlCatalog.Desktop
else if (args.Contains("--drm"))
{
SilenceConsole();
- return builder.StartLinuxDrm(args, scaling: GetScaling());
+ return builder.StartLinuxDrm(args, card: GetCard(), options: new DrmOutputOptions()
+ {
+ Scaling = GetScaling(),
+ Orientation = GetOrientation(),
+ });
}
else if (args.Contains("--dxgi"))
{
builder.With(new Win32PlatformOptions()
{
- CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain }
+ CompositionMode = [Win32CompositionMode.LowLatencyDxgiSwapChain]
});
return builder.StartWithClassicDesktopLifetime(args);
}
@@ -149,24 +162,17 @@ namespace ControlCatalog.Desktop
})
.UseSkia()
.WithInterFont()
+ .WithDeveloperTools()
.AfterSetup(builder =>
{
- if (!s_useFramebuffer)
- {
- builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
- {
- StartupScreenIndex = 1,
- });
- }
-
- EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
+ EmbedSample.Implementation = OperatingSystem.IsWindows() ? new EmbedSampleWin()
: OperatingSystem.IsMacOS() ? new EmbedSampleMac()
: OperatingSystem.IsLinux() ? new EmbedSampleGtk()
: null;
})
.LogToTrace();
- static void SilenceConsole()
+ private static void SilenceConsole()
{
new Thread(() =>
{
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 81caa6b2e4..c71e7a93ad 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -2,7 +2,6 @@
$(AvsCurrentTargetFramework)
true
- enable
true
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 5d6b5c6c86..d049e839be 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -35,6 +35,9 @@
+
+
+
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
index b682ebf51d..35e917f996 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
@@ -39,8 +39,12 @@
-
-
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
index d9e64ecee0..47b6eb7f4a 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
@@ -7,30 +7,12 @@ using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.LogicalTree;
+using ControlCatalog.Models;
namespace ControlCatalog.Pages
{
public partial class AutoCompleteBoxPage : UserControl
{
- public class StateData
- {
- public string Name { get; private set; }
- public string Abbreviation { get; private set; }
- public string Capital { get; private set; }
-
- public StateData(string name, string abbreviatoin, string capital)
- {
- Name = name;
- Abbreviation = abbreviatoin;
- Capital = capital;
- }
-
- public override string ToString()
- {
- return Name;
- }
- }
-
private static StateData[] BuildAllStates()
{
return new StateData[]
diff --git a/samples/ControlCatalog/Pages/BitmapCachePage.axaml b/samples/ControlCatalog/Pages/BitmapCachePage.axaml
new file mode 100644
index 0000000000..5902be8e51
--- /dev/null
+++ b/samples/ControlCatalog/Pages/BitmapCachePage.axaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+ Render at scale
+
+ Scale
+
+ Enable clear type
+
+ Snap to device pixels
+ Subpixel offset X
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs b/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs
new file mode 100644
index 0000000000..92b3cbec68
--- /dev/null
+++ b/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages;
+
+public partial class BitmapCachePage : UserControl
+{
+ public BitmapCachePage()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
index 7a22c0ddab..8734758d26 100644
--- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
@@ -5,7 +5,7 @@
x:Class="ControlCatalog.Pages.CalendarDatePickerPage">
A control for selecting dates with a calendar drop-down
-
+
+ PlaceholderText="Placeholder"/>
-
+ PlaceholderText="Floating Placeholder"
+ UseFloatingPlaceholder="True"/>
+
+
+
+
-
+
-
+
diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml b/samples/ControlCatalog/Pages/ClipboardPage.xaml
index 80b3f4d1ed..864a520aca 100644
--- a/samples/ControlCatalog/Pages/ClipboardPage.xaml
+++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml
@@ -1,4 +1,4 @@
-
@@ -21,7 +21,7 @@
+ PlaceholderText="Text to copy of file names per line" />
diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml
index 7320c1f3d7..e6b3568909 100644
--- a/samples/ControlCatalog/Pages/DialogsPage.xaml
+++ b/samples/ControlCatalog/Pages/DialogsPage.xaml
@@ -49,26 +49,17 @@
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
Desktop
@@ -80,17 +71,17 @@
-
+
-
+
+ PlaceholderText="Picked file content" />
diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
index 0e2ad7d2b9..892e320afc 100644
--- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
@@ -1,23 +1,14 @@
using System;
using System.Buffers;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Reflection;
using System.Security;
-using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
-using Avalonia.Controls.Presenters;
using Avalonia.Dialogs;
using Avalonia.Layout;
-using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
-using Avalonia.Platform.Storage.FileIO;
-
-#pragma warning disable CS0618 // Type or member is obsolete
-#nullable enable
namespace ControlCatalog.Pages
{
@@ -70,14 +61,6 @@ namespace ControlCatalog.Pages
};
- List GetFilters()
- {
- return GetFileTypes()?.Select(f => new FileDialogFilter
- {
- Name = f.Name, Extensions = f.Patterns!.ToList()
- }).ToList() ?? new List();
- }
-
List? BuildFileTypes()
{
var selectedItem = (FilterSelector.SelectedItem as ComboBoxItem)?.Content
@@ -168,88 +151,12 @@ namespace ControlCatalog.Pages
void UpdateSuggestedFilterSelectorState() =>
suggestedFilterSelector.IsEnabled = useSuggestedFilter.IsChecked == true;
- useSuggestedFilter.Checked += (_, _) => UpdateSuggestedFilterSelectorState();
- useSuggestedFilter.Unchecked += (_, _) => UpdateSuggestedFilterSelectorState();
+ useSuggestedFilter.IsCheckedChanged += (_, _) => UpdateSuggestedFilterSelectorState();
UpdateSuggestedFilterSelectorState();
FilterSelector.SelectionChanged += (_, _) => UpdateSuggestedFilterSelector(BuildFileTypes());
UpdateSuggestedFilterSelector(BuildFileTypes());
- OpenFile.Click += async delegate
- {
- // Almost guaranteed to exist
- var uri = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
- var initialFileName = uri == null ? null : System.IO.Path.GetFileName(uri);
- var initialDirectory = uri == null ? null : System.IO.Path.GetDirectoryName(uri);
-
- var result = await new OpenFileDialog()
- {
- Title = "Open file",
- Filters = GetFilters(),
- Directory = initialDirectory,
- InitialFileName = initialFileName
- }.ShowAsync(GetWindow());
- results.ItemsSource = result;
- resultsVisible.IsVisible = result?.Any() == true;
- };
- OpenMultipleFiles.Click += async delegate
- {
- var result = await new OpenFileDialog()
- {
- Title = "Open multiple files",
- Filters = GetFilters(),
- Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
- AllowMultiple = true
- }.ShowAsync(GetWindow());
- results.ItemsSource = result;
- resultsVisible.IsVisible = result?.Any() == true;
- };
- SaveFile.Click += async delegate
- {
- var filters = GetFilters();
- var result = await new SaveFileDialog()
- {
- Title = "Save file",
- Filters = filters,
- Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
- DefaultExtension = filters?.Any() == true ? "txt" : null,
- InitialFileName = "test.txt"
- }.ShowAsync(GetWindow());
- results.ItemsSource = new[] { result };
- resultsVisible.IsVisible = result != null;
- };
- SelectFolder.Click += async delegate
- {
- var result = await new OpenFolderDialog()
- {
- Title = "Select folder",
- Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
- }.ShowAsync(GetWindow());
- if (string.IsNullOrEmpty(result))
- {
- resultsVisible.IsVisible = false;
- }
- else
- {
- SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result!));
- results.ItemsSource = new[] { result };
- resultsVisible.IsVisible = true;
- }
- };
- OpenBoth.Click += async delegate
- {
- var result = await new OpenFileDialog()
- {
- Title = "Select both",
- Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
- AllowMultiple = true
- }.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
- {
- AllowDirectorySelection = true
- });
- results.ItemsSource = result;
- resultsVisible.IsVisible = result?.Any() == true;
- };
DecoratedWindow.Click += delegate
{
new DecoratedWindow().Show();
diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
index b01b4a93bc..a112f79c02 100644
--- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
+++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
@@ -1,4 +1,4 @@
-
- Watermark:
-
+ PlaceholderText:
+
Text:
@@ -81,23 +81,23 @@
+ PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
+ PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
-
+
+ PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}">
-
+
@@ -110,10 +110,18 @@
-
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml
index e2230e3cb6..346e561dfc 100644
--- a/samples/ControlCatalog/Pages/TextBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml
@@ -17,34 +17,41 @@
-
-
+
+
+
+
-
+
-
+
-
+
+ SelectionStart="5" SelectionEnd="22"
+ SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/>
@@ -54,11 +61,11 @@
-
+
+ FontFamily="Comic Sans MS"
+ FontSize="10"
+ Foreground="Red"/>
-
diff --git a/samples/ControlCatalog/Pages/ThemePage.axaml b/samples/ControlCatalog/Pages/ThemePage.axaml
index 2d948c44a0..7eb95471a0 100644
--- a/samples/ControlCatalog/Pages/ThemePage.axaml
+++ b/samples/ControlCatalog/Pages/ThemePage.axaml
@@ -1,4 +1,4 @@
-
-
-
+
+
diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props
index 9814914977..ae7dba55dc 100644
--- a/samples/Directory.Build.props
+++ b/samples/Directory.Build.props
@@ -4,7 +4,9 @@
false
$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\$(AvsCurrentTargetFramework)\Avalonia.Designer.HostApp.dll
false
+ false
14.0
+ enable
$(NoWarn);CS8002
diff --git a/samples/Generators.Sandbox/Controls/SignUpView.xaml b/samples/Generators.Sandbox/Controls/SignUpView.xaml
index b256d7a31c..09313705f4 100644
--- a/samples/Generators.Sandbox/Controls/SignUpView.xaml
+++ b/samples/Generators.Sandbox/Controls/SignUpView.xaml
@@ -1,4 +1,4 @@
-
+ PlaceholderText="Please, enter user name..."
+ UseFloatingPlaceholder="True" />
Exe
$(AvsCurrentTargetFramework)
true
- enable
- false
true
true
+
+
+ $(NoWarn);NU1510
-
diff --git a/samples/GpuInterop/MainWindow.axaml.cs b/samples/GpuInterop/MainWindow.axaml.cs
index 0cb3732b74..b0a719b95f 100644
--- a/samples/GpuInterop/MainWindow.axaml.cs
+++ b/samples/GpuInterop/MainWindow.axaml.cs
@@ -30,7 +30,6 @@ namespace GpuInterop
}
};
- this.AttachDevTools();
RendererDiagnostics.DebugOverlays = RendererDebugOverlays.Fps;
}
diff --git a/samples/GpuInterop/Program.cs b/samples/GpuInterop/Program.cs
index ef1b9c8951..46cf738304 100644
--- a/samples/GpuInterop/Program.cs
+++ b/samples/GpuInterop/Program.cs
@@ -34,6 +34,7 @@ namespace GpuInterop
UseDebug = true
}
})
+ .WithDeveloperTools()
.LogToTrace(LogEventLevel.Debug, "Vulkan");
}
}
diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj
index 6534787226..7e45643843 100644
--- a/samples/IntegrationTestApp/IntegrationTestApp.csproj
+++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj
@@ -2,7 +2,6 @@
WinExe
$(AvsCurrentTargetFramework)
- enable
$(NoWarn);AVP1012;AVLN3001
app.manifest
true
@@ -25,7 +24,6 @@
-
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index ee7c81bf22..894c052d6c 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -37,7 +37,7 @@ namespace IntegrationTestApp
{
Header = (string?)page.Name,
ToolTip = $"Tip:{(string?)page.Name}",
- ToggleType = NativeMenuItemToggleType.Radio,
+ ToggleType = MenuItemToggleType.Radio
};
menuItem.Click += (_, _) =>
@@ -82,6 +82,7 @@ namespace IntegrationTestApp
new("ComboBox", () => new ComboBoxPage()),
new("ContextMenu", () => new ContextMenuPage()),
new("DesktopPage", () => new DesktopPage()),
+ new("DragDrop", () => new DragDropPage()),
new("Embedding", () => new EmbeddingPage()),
new("Gestures", () => new GesturesPage()),
new("ListBox", () => new ListBoxPage()),
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/IntegrationTestApp/Pages/DragDropPage.axaml b/samples/IntegrationTestApp/Pages/DragDropPage.axaml
new file mode 100644
index 0000000000..64221759be
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/DragDropPage.axaml
@@ -0,0 +1,62 @@
+
+
+
+
+ Drop Position:
+
+ Status:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/DragDropPage.axaml.cs b/samples/IntegrationTestApp/Pages/DragDropPage.axaml.cs
new file mode 100644
index 0000000000..ba5ad0a3b5
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/DragDropPage.axaml.cs
@@ -0,0 +1,103 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class DragDropPage : UserControl
+{
+ public DragDropPage()
+ {
+ InitializeComponent();
+
+ // Set up drag-drop event handlers
+ AddHandler(DragDrop.DragOverEvent, DropTarget_DragOver);
+ AddHandler(DragDrop.DropEvent, DropTarget_Drop);
+ }
+
+ private async void DragSource_PointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ {
+ var dragData = new DataTransfer();
+ dragData.Add(DataTransferItem.CreateText("TestDragData"));
+
+ DragDropStatus.Text = "Dragging...";
+
+ var result = await DragDrop.DoDragDropAsync(e, dragData, DragDropEffects.Copy | DragDropEffects.Move);
+
+ DragDropStatus.Text = result switch
+ {
+ DragDropEffects.Copy => "Copied",
+ DragDropEffects.Move => "Moved",
+ DragDropEffects.None => "Cancelled",
+ _ => $"Result: {result}"
+ };
+ }
+ }
+
+ private void DropTarget_DragOver(object? sender, DragEventArgs e)
+ {
+ // Only handle events for the drop target
+ if (e.Source != DropTarget && !IsChildOf(e.Source as Visual, DropTarget))
+ return;
+
+ e.DragEffects = DragDropEffects.Copy;
+
+ // Get the position relative to the drop target
+ var position = e.GetPosition(DropTarget);
+ DropPosition.Text = $"DragOver: ({position.X:F0}, {position.Y:F0})";
+ }
+
+ private void DropTarget_Drop(object? sender, DragEventArgs e)
+ {
+ // Only handle events for the drop target
+ if (e.Source != DropTarget && !IsChildOf(e.Source as Visual, DropTarget))
+ return;
+
+ // Get the position relative to the drop target
+ var position = e.GetPosition(DropTarget);
+ DropPosition.Text = $"Drop: ({position.X:F0}, {position.Y:F0})";
+
+ // Check if the position is within reasonable bounds of the drop target
+ var bounds = DropTarget.Bounds;
+ var isWithinBounds = position.X >= 0 && position.X <= bounds.Width &&
+ position.Y >= 0 && position.Y <= bounds.Height;
+
+ var text = e.DataTransfer.TryGetText();
+ if (text != null)
+ {
+ DropTargetText.Text = isWithinBounds
+ ? $"Dropped: {text} at ({position.X:F0}, {position.Y:F0})"
+ : $"ERROR: Position out of bounds! ({position.X:F0}, {position.Y:F0})";
+ DragDropStatus.Text = isWithinBounds ? "Drop OK" : "Drop position ERROR";
+ }
+
+ e.DragEffects = DragDropEffects.Copy;
+ }
+
+ private static bool IsChildOf(Visual? child, Visual? parent)
+ {
+ if (child == null || parent == null)
+ return false;
+
+ var current = child.Parent as Visual;
+ while (current != null)
+ {
+ if (current == parent)
+ return true;
+ current = current.Parent as Visual;
+ }
+ return false;
+ }
+
+ private void ResetDragDrop_Click(object? sender, RoutedEventArgs e)
+ {
+ DropPosition.Text = string.Empty;
+ DragDropStatus.Text = string.Empty;
+ DropTargetText.Text = "Drop items here";
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml
index 632893bcd0..4d6bc24b47 100644
--- a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml
+++ b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml
@@ -16,5 +16,7 @@
+
+
diff --git a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs
index 93855cd13d..465743afbf 100644
--- a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs
+++ b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs
@@ -1,10 +1,19 @@
+using System;
+using Avalonia.Automation;
using Avalonia.Controls;
+using Avalonia.Controls.Embedding;
using Avalonia.Interactivity;
+using IntegrationTestApp.Embedding;
+using MonoMac.AppKit;
+using MonoMac.CoreGraphics;
+using MonoMac.ObjCRuntime;
namespace IntegrationTestApp;
public partial class EmbeddingPage : UserControl
{
+ private const long NSModalResponseContinue = -1002;
+
public EmbeddingPage()
{
InitializeComponent();
@@ -19,5 +28,65 @@ public partial class EmbeddingPage : UserControl
private void Reset_Click(object? sender, RoutedEventArgs e)
{
ResetText();
+ ModalResultTextBox.Text = "";
+ }
+
+ private void RunNativeModalSession_OnClick(object? sender, RoutedEventArgs e)
+ {
+ MacHelper.EnsureInitialized();
+
+ var app = NSApplication.SharedApplication;
+ var modalWindow = CreateNativeWindow();
+ var session = app.BeginModalSession(modalWindow);
+
+ while (true)
+ {
+ if (app.RunModalSession(session) != NSModalResponseContinue)
+ break;
+ }
+
+ app.EndModalSession(session);
+ }
+
+ private NSWindow CreateNativeWindow()
+ {
+ var button = new Button
+ {
+ Name = "ButtonInModal",
+ Content = "Button"
+ };
+
+ AutomationProperties.SetAutomationId(button, "ButtonInModal");
+
+ var root = new EmbeddableControlRoot
+ {
+ Width = 200,
+ Height = 200,
+ Content = button
+ };
+ root.Prepare();
+
+ var window = new NSWindow(
+ new CGRect(0, 0, root.Width, root.Height),
+ NSWindowStyle.Titled | NSWindowStyle.Closable,
+ NSBackingStore.Buffered,
+ false);
+
+ window.Identifier = "ModalNativeWindow";
+ window.WillClose += (_, _) => NSApplication.SharedApplication.StopModal();
+
+ button.Click += (_, _) =>
+ {
+ ModalResultTextBox.Text = "Clicked";
+ window.Close();
+ };
+
+ if (root.TryGetPlatformHandle() is not { } handle)
+ throw new InvalidOperationException("Could not get platform handle");
+
+ window.ContentView = (NSView)Runtime.GetNSObject(handle.Handle)!;
+ root.StartRendering();
+
+ return window;
}
}
diff --git a/samples/IntegrationTestApp/Pages/ScreensPage.axaml b/samples/IntegrationTestApp/Pages/ScreensPage.axaml
index 2d95c4719a..7f0b341714 100644
--- a/samples/IntegrationTestApp/Pages/ScreensPage.axaml
+++ b/samples/IntegrationTestApp/Pages/ScreensPage.axaml
@@ -8,12 +8,12 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
index d6b418952a..0a7dce9c02 100644
--- a/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
+++ b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
@@ -10,7 +10,7 @@
-
+
diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml b/samples/IntegrationTestApp/Pages/WindowPage.axaml
index 60900a4778..797c1c7170 100644
--- a/samples/IntegrationTestApp/Pages/WindowPage.axaml
+++ b/samples/IntegrationTestApp/Pages/WindowPage.axaml
@@ -6,7 +6,7 @@
x:Class="IntegrationTestApp.Pages.WindowPage">
-
+
NonOwned
Owned
diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
index b7f505a7b2..baae2b8766 100644
--- a/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
+++ b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
@@ -136,6 +136,7 @@ public partial class WindowPage : UserControl
Width = 200,
Height = 200,
Background = Brushes.Green,
+ SystemDecorations = SystemDecorations.None,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Content = new Border
{
@@ -145,7 +146,7 @@ public partial class WindowPage : UserControl
}
};
- backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
+ popup.PointerPressed += (_, _) => backgroundWindow.Close();
backgroundWindow.Show(Window);
popup.Open();
diff --git a/samples/IntegrationTestApp/Program.cs b/samples/IntegrationTestApp/Program.cs
index 43c936bb1c..29b7557f6a 100644
--- a/samples/IntegrationTestApp/Program.cs
+++ b/samples/IntegrationTestApp/Program.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using Avalonia;
using IntegrationTestApp.Embedding;
@@ -32,6 +31,7 @@ namespace IntegrationTestApp
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
+ .WithDeveloperTools()
.AfterSetup(builder =>
{
NativeTextBox.Factory =
diff --git a/samples/MiniMvvm/MiniCommand.cs b/samples/MiniMvvm/MiniCommand.cs
index b39ceced3d..57f6d6b4d8 100644
--- a/samples/MiniMvvm/MiniCommand.cs
+++ b/samples/MiniMvvm/MiniCommand.cs
@@ -58,10 +58,10 @@ namespace MiniMvvm
{
public static MiniCommand Create(Action cb) => new MiniCommand
Use Safe Area
Automatic Paddings
Hide System Bars
-
+
diff --git a/samples/SampleControls/ControlSamples.csproj b/samples/SampleControls/ControlSamples.csproj
index 405175d01c..9abeaabd91 100644
--- a/samples/SampleControls/ControlSamples.csproj
+++ b/samples/SampleControls/ControlSamples.csproj
@@ -2,7 +2,6 @@
$(AvsCurrentTargetFramework)
- enable
diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs
index 52321b46a9..b676992111 100644
--- a/samples/Sandbox/Program.cs
+++ b/samples/Sandbox/Program.cs
@@ -10,6 +10,7 @@ namespace Sandbox
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure()
.UsePlatformDetect()
+ .WithDeveloperTools()
.LogToTrace();
}
}
diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj
index 571149950f..e372366391 100644
--- a/samples/Sandbox/Sandbox.csproj
+++ b/samples/Sandbox/Sandbox.csproj
@@ -9,7 +9,6 @@
-
diff --git a/samples/SingleProjectSandbox/MainView.axaml b/samples/SingleProjectSandbox/MainView.axaml
index e1764b0795..b7f1bdaa53 100644
--- a/samples/SingleProjectSandbox/MainView.axaml
+++ b/samples/SingleProjectSandbox/MainView.axaml
@@ -5,10 +5,10 @@
x:DataType="local:MainView">
-
-
-
-
+
+
+
+
diff --git a/samples/SingleProjectSandbox/SingleProjectSandbox.csproj b/samples/SingleProjectSandbox/SingleProjectSandbox.csproj
index 4c7c49ed3e..8bef1d53d7 100644
--- a/samples/SingleProjectSandbox/SingleProjectSandbox.csproj
+++ b/samples/SingleProjectSandbox/SingleProjectSandbox.csproj
@@ -5,7 +5,6 @@
$(TargetFrameworks);$(AvsCurrentMacOSTargetFramework)
Exe
true
- enable
true
diff --git a/samples/TextTestApp/InteractiveLineControl.cs b/samples/TextTestApp/InteractiveLineControl.cs
index f4e26e1415..e1bc171952 100644
--- a/samples/TextTestApp/InteractiveLineControl.cs
+++ b/samples/TextTestApp/InteractiveLineControl.cs
@@ -233,6 +233,7 @@ namespace TextTestApp
}
private GenericTextRunProperties? _textRunProperties;
+
public GenericTextRunProperties TextRunProperties
{
get
@@ -241,9 +242,6 @@ namespace TextTestApp
}
set
{
- if (value == null)
- throw new ArgumentNullException(nameof(value));
-
_textRunProperties = value;
SetCurrentValue(FontFamilyProperty, value.Typeface.FontFamily);
SetCurrentValue(FontFeaturesProperty, value.FontFeatures);
@@ -257,12 +255,15 @@ namespace TextTestApp
private GenericTextRunProperties CreateTextRunProperties()
{
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
- return new GenericTextRunProperties(typeface, FontFeatures, FontSize,
+ return new GenericTextRunProperties(
+ typeface,
+ FontSize,
textDecorations: null,
foregroundBrush: Brushes.Black,
backgroundBrush: null,
baselineAlignment: BaselineAlignment.Baseline,
- cultureInfo: null);
+ cultureInfo: null,
+ fontFeatures: FontFeatures);
}
// TextParagraphProperties
@@ -338,7 +339,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/samples/TextTestApp/MainWindow.axaml.cs b/samples/TextTestApp/MainWindow.axaml.cs
index 493bc3e9d4..239c6087d8 100644
--- a/samples/TextTestApp/MainWindow.axaml.cs
+++ b/samples/TextTestApp/MainWindow.axaml.cs
@@ -223,12 +223,12 @@ namespace TextTestApp
}
}
- private IImage CreateGlyphDrawing(IGlyphTypeface glyphTypeface, double emSize, GlyphInfo info)
+ private IImage CreateGlyphDrawing(GlyphTypeface glyphTypeface, double emSize, GlyphInfo info)
{
return new DrawingImage { Drawing = new GeometryDrawing { Brush = Brushes.Black, Geometry = GetGlyphOutline(glyphTypeface, emSize, info) } };
}
- private Geometry GetGlyphOutline(IGlyphTypeface typeface, double emSize, GlyphInfo info)
+ private Geometry GetGlyphOutline(GlyphTypeface typeface, double emSize, GlyphInfo info)
{
// substitute for GlyphTypeface.GetGlyphOutline
return new GlyphRun(typeface, emSize, new[] { '\0' }, [info]).BuildGeometry();
@@ -320,11 +320,17 @@ namespace TextTestApp
private void OnBufferSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
- List rectangles = new List(_buffer.Selection.Count);
+ if (_selectionAdorner is null)
+ return;
- foreach (var row in _buffer.SelectedItems)
- if (row is Control { Tag: Rect rect })
- rectangles.Add(rect);
+ var rectangles = new List(_buffer.Selection.Count);
+
+ if (_buffer.SelectedItems is { } selectedItems)
+ {
+ foreach (var row in selectedItems)
+ if (row is Control { Tag: Rect rect })
+ rectangles.Add(rect);
+ }
_selectionAdorner.Rectangles = rectangles;
}
diff --git a/samples/TextTestApp/Program.cs b/samples/TextTestApp/Program.cs
index c3f4de5d3e..e3931f97d8 100644
--- a/samples/TextTestApp/Program.cs
+++ b/samples/TextTestApp/Program.cs
@@ -22,8 +22,9 @@ namespace TextTestApp
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure()
- .UsePlatformDetect()
- .LogToTrace();
+ .UsePlatformDetect()
+ .WithDeveloperTools()
+ .LogToTrace();
}
}
}
diff --git a/samples/TextTestApp/TextTestApp.csproj b/samples/TextTestApp/TextTestApp.csproj
index 50dc52c768..c960529f76 100644
--- a/samples/TextTestApp/TextTestApp.csproj
+++ b/samples/TextTestApp/TextTestApp.csproj
@@ -6,11 +6,9 @@
true
app.manifest
true
- enable
-
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
index aee3a60a01..8e9c390c94 100644
--- a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
@@ -29,10 +29,6 @@ public partial class MainWindow : Window
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
- if (Debugger.IsAttached)
- {
- this.AttachDevTools();
- }
}
private PlugTool? _plugTool;
protected override void OnOpened(EventArgs e)
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
index 8508765545..dfde281781 100644
--- a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
@@ -3,7 +3,6 @@
WinExe
$(AvsCurrentTargetFramework)
- enable
true
app.manifest
true
@@ -24,7 +23,6 @@
-
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
index 5d43f20cc4..0fd79cf3eb 100644
--- a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
@@ -2,7 +2,6 @@
$(AvsCurrentTargetFramework)
- enable
true
app.manifest
true
@@ -33,7 +32,6 @@
-
diff --git a/samples/VirtualizationDemo/MainWindow.axaml.cs b/samples/VirtualizationDemo/MainWindow.axaml.cs
index 533dc00aa1..e725603b7e 100644
--- a/samples/VirtualizationDemo/MainWindow.axaml.cs
+++ b/samples/VirtualizationDemo/MainWindow.axaml.cs
@@ -1,4 +1,3 @@
-using Avalonia;
using Avalonia.Controls;
using VirtualizationDemo.ViewModels;
@@ -9,7 +8,6 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
- this.AttachDevTools();
DataContext = new MainWindowViewModel();
}
}
diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs
index 87212b6daa..dd289b3b25 100644
--- a/samples/VirtualizationDemo/Program.cs
+++ b/samples/VirtualizationDemo/Program.cs
@@ -7,6 +7,7 @@ class Program
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
+ .WithDeveloperTools()
.LogToTrace();
public static int Main(string[] args)
diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj
index 32bc379f42..ee0fe7c6a1 100644
--- a/samples/VirtualizationDemo/VirtualizationDemo.csproj
+++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj
@@ -6,8 +6,6 @@
-
-
@@ -18,6 +16,7 @@
PreserveNewest
+
diff --git a/samples/XEmbedSample/XEmbedSample.csproj b/samples/XEmbedSample/XEmbedSample.csproj
index 89bbf13273..2e34bb2cc7 100644
--- a/samples/XEmbedSample/XEmbedSample.csproj
+++ b/samples/XEmbedSample/XEmbedSample.csproj
@@ -4,16 +4,8 @@
Exe
$(AvsCurrentTargetFramework)
enable
- enable
true
-
- Exe
- $(AvsCurrentTargetFramework)
- enable
- enable
- true
-
diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
index 5cb227f9cb..576910ca3d 100644
--- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
+++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
@@ -8,7 +8,6 @@
-
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index d9e3e299bd..5316a84570 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -24,6 +24,7 @@ namespace Avalonia
return builder
.UseAndroidRuntimePlatformSubsystem()
.UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
+ .UseHarfBuzz()
.UseSkia();
}
}
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index 97acf3d012..0c1100ab5f 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -4,14 +4,18 @@
$(AvsMinSupportedAndroidVersion)
true
Avalonia.Android.Internal
+
+
+ $(NoWarn);CS8002
-
+
+
diff --git a/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs b/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
index faacff7554..ac92aafc29 100644
--- a/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
+++ b/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using Android.OS;
using AndroidX.Core.View.Accessibility;
@@ -157,7 +158,7 @@ namespace Avalonia.Android
if (labeledBy is not null)
{
GetOrCreateNodeInfoProvidersFromPeer(labeledBy, out int labeledById);
- nodeInfo.SetLabeledBy(_view, labeledById);
+ nodeInfo.AddLabeledBy(_view, labeledById);
}
// UI debug metadata
@@ -181,7 +182,7 @@ namespace Avalonia.Android
_view.TopLevelImpl.PointToScreen(bounds.TopLeft),
_view.TopLevelImpl.PointToScreen(bounds.BottomRight)
);
- nodeInfo.SetBoundsInParent(new(
+ nodeInfo.SetBoundsInScreen(new(
screenRect.X, screenRect.Y,
screenRect.Right, screenRect.Bottom
));
diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs
index 8577b54b0e..60345e0a50 100644
--- a/src/Android/Avalonia.Android/AvaloniaActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs
@@ -20,13 +20,15 @@ namespace Avalonia.Android;
/// Common implementation of android activity that is integrated with Avalonia views.
/// If you need a base class for main activity of Avalonia app, see .
///
-public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity, IOnBackInvokedCallback
+public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
{
private EventHandler? _onActivated, _onDeactivated;
private GlobalLayoutListener? _listener;
private object? _content;
private bool _contentViewSet;
internal AvaloniaView? _view;
+ private BackPressedCallback? _currentBackPressedCallback;
+ private bool _shouldNavigateBack;
public Action? ActivityResult { get; set; }
public Action? RequestPermissionsResult { get; set; }
@@ -63,6 +65,20 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity, IOnBackInv
}
}
+ ///
+ /// Gets whether to call the default back handler after our back handler is called.
+ ///
+ internal bool ShouldNavigateBack
+ {
+ get
+ {
+ var goBack = _shouldNavigateBack;
+ _shouldNavigateBack = false;
+
+ return goBack;
+ }
+ }
+
event EventHandler? IAvaloniaActivity.Activated
{
add { _onActivated += value; }
@@ -103,22 +119,14 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity, IOnBackInv
activatableLifetime.CurrentIntendActivity = this;
}
- if (Intent?.Data is { } androidUri
- && androidUri.IsAbsolute
- && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri))
- {
- if (uri.Scheme == Uri.UriSchemeFile)
- {
- if (AndroidStorageItem.CreateItem(this, androidUri) is { } item)
- {
- _onActivated?.Invoke(this, new FileActivatedEventArgs(new [] { item }));
- }
- }
- else
- {
- _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
- }
- }
+ HandleIntent(Intent);
+ }
+
+ protected override void OnNewIntent(Intent? intent)
+ {
+ base.OnNewIntent(intent);
+
+ HandleIntent(intent);
}
protected override void OnStop()
@@ -127,7 +135,8 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity, IOnBackInv
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
- OnBackInvokedDispatcher.UnregisterOnBackInvokedCallback(this);
+ _currentBackPressedCallback?.Remove();
+ _currentBackPressedCallback = null;
}
base.OnStop();
}
@@ -138,7 +147,8 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity, IOnBackInv
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
- OnBackInvokedDispatcher.RegisterOnBackInvokedCallback(IOnBackInvokedDispatcher.PriorityDefault, this);
+ _currentBackPressedCallback = new BackPressedCallback(this);
+ OnBackPressedDispatcher.AddCallback(this, _currentBackPressedCallback);
}
base.OnStart();
}
@@ -200,11 +210,33 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity, IOnBackInv
_view = new AvaloniaView(this) { Content = initialContent };
}
+ private void HandleIntent(Intent? intent)
+ {
+ if (intent?.Data is { } androidUri
+ && androidUri.IsAbsolute
+ && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri))
+ {
+ if (uri.Scheme == Uri.UriSchemeFile || uri.Scheme == "content")
+ {
+ if (AndroidStorageItem.CreateItem(this, androidUri) is { } item)
+ {
+ _onActivated?.Invoke(this, new FileActivatedEventArgs(new[] { item }));
+ }
+ }
+ else
+ {
+ _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
+ }
+ }
+ }
+
public void OnBackInvoked()
{
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
+
+ _shouldNavigateBack = !eventArgs.Handled;
}
private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
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/BackPressedCallback.cs b/src/Android/Avalonia.Android/BackPressedCallback.cs
new file mode 100644
index 0000000000..993c19b700
--- /dev/null
+++ b/src/Android/Avalonia.Android/BackPressedCallback.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using AndroidX.Activity;
+
+namespace Avalonia.Android
+{
+ internal class BackPressedCallback(AvaloniaActivity activity) : OnBackPressedCallback(true)
+ {
+ public override void HandleOnBackPressed()
+ {
+ activity.OnBackInvoked();
+
+ if (activity.ShouldNavigateBack)
+ {
+ this.Enabled = false;
+ activity.OnBackPressedDispatcher?.OnBackPressed();
+ }
+
+ this.Enabled = true;
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/ChoreographerTimer.cs b/src/Android/Avalonia.Android/ChoreographerTimer.cs
index 4d1d1e1c20..adca9c72ce 100644
--- a/src/Android/Avalonia.Android/ChoreographerTimer.cs
+++ b/src/Android/Avalonia.Android/ChoreographerTimer.cs
@@ -13,7 +13,6 @@ namespace Avalonia.Android
{
internal sealed class ChoreographerTimer : IRenderTimer
{
- private static readonly bool s_supports64Callback = OperatingSystem.IsAndroidVersionAtLeast(29);
private readonly object _lock = new();
private readonly TaskCompletionSource _choreographer = new();
private readonly AutoResetEvent _event = new(false);
@@ -126,7 +125,7 @@ namespace Avalonia.Android
private static unsafe void PostFrameCallback(IntPtr choreographer, IntPtr data)
{
// AChoreographer_postFrameCallback is deprecated on 10.0+.
- if (s_supports64Callback)
+ if (OperatingSystem.IsAndroidVersionAtLeast(29))
{
AChoreographer_postFrameCallback64(choreographer, &FrameCallback64, data);
}
diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
index 45345f9da7..a8ab825808 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
@@ -302,19 +302,22 @@ namespace Avalonia.Android.Platform
_activity.Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
var androidColor = global::Android.Graphics.Color.Argb(color.A, color.R, color.G, color.B);
- _activity.Window.SetStatusBarColor(androidColor);
- if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
+ // Status and navigation bar colors can't be changed at all with API level >= 35
+ if (!OperatingSystem.IsAndroidVersionAtLeast(35))
{
- // As we can only change the navigation bar's foreground api 26 and newer, we only change the background color if running on those versions
- _activity.Window.SetNavigationBarColor(androidColor);
+ _activity.Window.SetStatusBarColor(androidColor);
+
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
+ {
+ // As we can only change the navigation bar's foreground api 26 and newer, we only change the background color if running on those versions
+ _activity.Window.SetNavigationBarColor(androidColor);
+ }
}
}
}
}
- public bool DisplayEdgeToEdge { get => DisplaysEdgeToEdge; set => DisplayEdgeToEdgePreference = value; }
-
public bool DisplaysEdgeToEdge => _displaysEdgeToEdge;
internal void ApplyStatusBarState()
diff --git a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs
index 3f1f9bf44e..9b0f1bee38 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs
@@ -10,6 +10,7 @@ using AndroidX.Window.Layout;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Platform;
using AndroidOrientation = global::Android.Content.Res.Orientation;
+using AndroidRotation = global::Android.Views.SurfaceOrientation;
namespace Avalonia.Android.Platform;
@@ -53,7 +54,7 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl
var orientation = displayContext.Resources?.Configuration?.Orientation;
if (orientation == AndroidOrientation.Square)
naturalOrientation = ScreenOrientation.None;
- else if (rotation is SurfaceOrientation.Rotation0 or SurfaceOrientation.Rotation180)
+ else if (rotation is AndroidRotation.Rotation0 or AndroidRotation.Rotation180)
naturalOrientation = orientation == AndroidOrientation.Landscape ?
ScreenOrientation.Landscape :
ScreenOrientation.Portrait;
@@ -73,14 +74,14 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl
CurrentOrientation = (display.Rotation, naturalOrientation) switch
{
(_, ScreenOrientation.None) => ScreenOrientation.None,
- (SurfaceOrientation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape,
- (SurfaceOrientation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait,
- (SurfaceOrientation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped,
- (SurfaceOrientation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped,
- (SurfaceOrientation.Rotation0, _) => ScreenOrientation.Portrait,
- (SurfaceOrientation.Rotation90, _) => ScreenOrientation.Landscape,
- (SurfaceOrientation.Rotation180, _) => ScreenOrientation.PortraitFlipped,
- (SurfaceOrientation.Rotation270, _) => ScreenOrientation.LandscapeFlipped,
+ (AndroidRotation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape,
+ (AndroidRotation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait,
+ (AndroidRotation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped,
+ (AndroidRotation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped,
+ (AndroidRotation.Rotation0, _) => ScreenOrientation.Portrait,
+ (AndroidRotation.Rotation90, _) => ScreenOrientation.Landscape,
+ (AndroidRotation.Rotation180, _) => ScreenOrientation.PortraitFlipped,
+ (AndroidRotation.Rotation270, _) => ScreenOrientation.LandscapeFlipped,
_ => ScreenOrientation.Portrait
};
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
index 7a0d9793a3..87710127aa 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
@@ -25,10 +25,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
Size = new PixelSize(rc.right, rc.bottom);
ANativeWindow_lock(_window, &buffer, &rc);
- Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565
- ? PixelFormat.Rgb565 : PixelFormat.Rgba8888;
+ (Format, AlphaFormat, RowBytes) = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 ?
+ (PixelFormat.Rgb565, AlphaFormat.Opaque, buffer.stride * 2) :
+ (PixelFormat.Rgba8888, AlphaFormat.Premul, buffer.stride * 4);
- RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4);
Address = buffer.bits;
Dpi = new Vector(96, 96) * scaling;
@@ -46,6 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
+ public AlphaFormat AlphaFormat { get; }
[DllImport("android")]
internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle);
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
index 7a495c4221..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,7 +63,7 @@ namespace Avalonia.Android
.Log(this, "InvalidationAwareSurfaceView Destroyed");
ReleaseNativeWindowHandle();
_size = new PixelSize(1, 1);
- _scaling = 1;
+ SurfaceWindowDestroyed?.Invoke(this, EventArgs.Empty);
}
public virtual void SurfaceRedrawNeeded(ISurfaceHolder holder)
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 8f133f266d..42f0877eca 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -26,7 +26,7 @@ using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform
{
- class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfoWithWaitPolicy
+ class TopLevelImpl : ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfoWithWaitPolicy
{
private readonly AndroidKeyboardEventsHelper _keyboardHelper;
private readonly AndroidMotionEventsHelper _pointerHelper;
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
index 58fc59f804..03be7c2153 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
@@ -8,7 +8,7 @@ using Avalonia.Input.Raw;
namespace Avalonia.Android.Platform.Specific.Helpers
{
- internal class AndroidKeyboardEventsHelper : IDisposable where TView : TopLevelImpl, IAndroidView
+ internal class AndroidKeyboardEventsHelper : IDisposable where TView : TopLevelImpl
{
private readonly TView _view;
@@ -65,8 +65,8 @@ namespace Avalonia.Android.Platform.Specific.Helpers
AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e),
physicalKey,
- keyDeviceType,
- keySymbol);
+ keySymbol,
+ keyDeviceType);
_view.Input?.Invoke(rawKeyEvent);
diff --git a/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs b/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
deleted file mode 100644
index 056912835a..0000000000
--- a/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-using Android.Views;
-
-namespace Avalonia.Android.Platform.Specific
-{
- public interface IAndroidView
- {
- [Obsolete("Use TopLevel.TryGetPlatformHandle instead, which can be casted to AndroidViewControlHandle.")]
- View View { get; }
- }
-}
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
index 69329ea85f..b42d0f7aaa 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
@@ -202,7 +202,7 @@ internal class AndroidStorageProvider : IStorageProvider
public async Task SaveFilePickerWithResultAsync(FilePickerSaveOptions options)
{
var file = await SaveFilePickerAsync(options).ConfigureAwait(false);
- return new SaveFilePickerResult(file);
+ return new SaveFilePickerResult { File = file };
}
public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options)
diff --git a/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
index b07bd686b8..da3c4cb8e6 100644
--- a/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
+++ b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
@@ -7,17 +7,6 @@ namespace Avalonia.Animation;
partial class Animation
{
- ///
- /// Sets the value of the Animator attached property for a setter.
- ///
- /// The animation setter.
- /// The property animator value.
- [Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator", true)]
- public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
- {
- s_animators[setter] = (value.WrapperType, value.CreateWrapper);
- }
-
///
/// Sets the value of the Animator attached property for a setter.
///
@@ -92,4 +81,4 @@ partial class Animation
return null;
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs
index 7e139c5ae6..0391280ede 100644
--- a/src/Avalonia.Base/Animation/Animation.cs
+++ b/src/Avalonia.Base/Animation/Animation.cs
@@ -271,7 +271,7 @@ namespace Avalonia.Animation
IDisposable IAnimation.Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete)
=> Apply(control, clock, match, onComplete);
- ///
+ ///
internal IDisposable Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete)
{
var (animators, subscriptions) = InterpretKeyframes(control);
@@ -320,7 +320,7 @@ namespace Avalonia.Animation
public Task RunAsync(Animatable control, CancellationToken cancellationToken = default) =>
RunAsync(control, null, cancellationToken);
- ///
+ ///
internal Task RunAsync(Animatable control, IClock? clock)
{
return RunAsync(control, clock, default);
@@ -329,7 +329,7 @@ namespace Avalonia.Animation
Task IAnimation.RunAsync(Animatable control, IClock? clock, CancellationToken cancellationToken)
=> RunAsync(control, clock, cancellationToken);
- ///
+ ///
internal Task RunAsync(Animatable control, IClock? clock, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs
index ec27978939..3be646d66c 100644
--- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs
+++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs
@@ -137,7 +137,8 @@ namespace Avalonia.Animation
if (!_gotFirstKFValue)
{
- _firstKFValue = (T)_animator.First().Value!;
+ var firstKeyFrame = _animator.First();
+ _firstKFValue = firstKeyFrame.Value is T value ? value : _neutralValue;
_gotFirstKFValue = true;
}
}
diff --git a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
index b7f1f08e89..5786119fb9 100644
--- a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
+++ b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
@@ -46,7 +46,7 @@ namespace Avalonia.Animation
Property = setter.Property;
var value = setter.Value;
- if (value is IBinding binding)
+ if (value is BindingBase binding)
{
return Bind(ValueProperty, binding, targetControl);
}
diff --git a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs b/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs
deleted file mode 100644
index 2e43e97da2..0000000000
--- a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace Avalonia.Animation.Easings;
-
-[Obsolete("Use SplineEasing instead")]
-public sealed class CubicBezierEasing : IEasing
-{
- private CubicBezierEasing()
- {
- }
-
- public Point ControlPoint2 { get; set; }
- public Point ControlPoint1 { get; set; }
-
- double IEasing.Ease(double progress)
- => throw new NotSupportedException();
-}
diff --git a/src/Avalonia.Base/Animation/ICustomAnimator.cs b/src/Avalonia.Base/Animation/ICustomAnimator.cs
index 119a6115da..7155af1223 100644
--- a/src/Avalonia.Base/Animation/ICustomAnimator.cs
+++ b/src/Avalonia.Base/Animation/ICustomAnimator.cs
@@ -2,34 +2,6 @@ using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation;
-[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator", true)]
-public abstract class CustomAnimatorBase
-{
- internal abstract IAnimator CreateWrapper();
- internal abstract Type WrapperType { get; }
-}
-
-[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator", true)]
-public abstract class CustomAnimatorBase : CustomAnimatorBase
-{
- public abstract T Interpolate(double progress, T oldValue, T newValue);
-
- internal override Type WrapperType => typeof(AnimatorWrapper);
- internal override IAnimator CreateWrapper() => new AnimatorWrapper(this);
-
- internal class AnimatorWrapper : Animator
- {
- private readonly CustomAnimatorBase _parent;
-
- public AnimatorWrapper(CustomAnimatorBase parent)
- {
- _parent = parent;
- }
-
- public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
- }
-}
-
public interface ICustomAnimator
{
internal IAnimator CreateWrapper();
@@ -55,4 +27,4 @@ public abstract class InterpolatingAnimator : ICustomAnimator
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Animation/TransitionBase.cs b/src/Avalonia.Base/Animation/TransitionBase.cs
index 86d997a7d0..864cd1bf7a 100644
--- a/src/Avalonia.Base/Animation/TransitionBase.cs
+++ b/src/Avalonia.Base/Animation/TransitionBase.cs
@@ -78,7 +78,7 @@ namespace Avalonia.Animation
set { SetAndRaise(EasingProperty, ref _easing, value); }
}
- ///
+ ///
[DisallowNull]
public AvaloniaProperty? Property
{
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index cc93208e51..3303ed276e 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -98,7 +98,7 @@ namespace Avalonia
/// Gets or sets a binding for a .
///
/// The binding information.
- public IBinding this[IndexerDescriptor binding]
+ public BindingBase this[IndexerDescriptor binding]
{
get { return new IndexerBinding(this, binding.Property!, binding.Mode); }
set { this.Bind(binding.Property!, value); }
@@ -417,14 +417,14 @@ namespace Avalonia
}
///
- /// Binds a to an .
+ /// Binds a to an .
///
/// The property.
/// The binding.
///
/// The binding expression which represents the binding instance on this object.
///
- public BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding)
+ public BindingExpressionBase Bind(AvaloniaProperty property, BindingBase binding)
{
return Bind(property, binding, null);
}
@@ -474,9 +474,9 @@ namespace Avalonia
VerifyAccess();
ValidatePriority(priority);
- if (source is IBinding2 b)
+ if (source is BindingBase b)
{
- if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression)
+ if (b.CreateInstance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
if (priority != expression.Priority)
@@ -574,9 +574,9 @@ namespace Avalonia
throw new ArgumentException($"The property {property.Name} is readonly.");
}
- if (source is IBinding2 b)
+ if (source is BindingBase b)
{
- if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression)
+ if (b.CreateInstance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
return GetValueStore().AddBinding(property, expression);
}
@@ -643,7 +643,7 @@ namespace Avalonia
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
///
- /// Binds a to an .
+ /// Binds a to an .
///
/// The property.
/// The binding.
@@ -656,11 +656,9 @@ namespace Avalonia
///
/// The binding expression which represents the binding instance on this object.
///
- internal BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding, object? anchor)
+ internal BindingExpressionBase Bind(AvaloniaProperty property, BindingBase binding, object? anchor)
{
- if (binding is not IBinding2 b)
- throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'.");
- if (b.Instance(this, property, anchor) is not UntypedBindingExpressionBase expression)
+ if (binding.CreateInstance(this, property, anchor) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
return GetValueStore().AddBinding(property, expression);
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index 76138beb4d..3c9c047684 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -11,12 +11,12 @@ namespace Avalonia
public static class AvaloniaObjectExtensions
{
///
- /// Converts an to an .
+ /// Converts an to an .
///
/// The type produced by the observable.
/// The observable
- /// An .
- public static IBinding ToBinding(this IObservable source)
+ /// An .
+ public static BindingBase ToBinding(this IObservable source)
{
return new BindingAdaptor(
typeof(T).IsValueType
@@ -227,33 +227,6 @@ namespace Avalonia
};
}
- ///
- /// Binds a property on an to an .
- ///
- /// The object.
- /// The property to bind.
- /// The binding.
- ///
- /// An optional anchor from which to locate required context. When binding to objects that
- /// are not in the logical tree, certain types of binding need an anchor into the tree in
- /// order to locate named controls or resources. The parameter
- /// can be used to provide this context.
- ///
- /// An which can be used to cancel the binding.
- [Obsolete("Use AvaloniaObject.Bind(AvaloniaProperty, IBinding")]
- public static IDisposable Bind(
- this AvaloniaObject target,
- AvaloniaProperty property,
- IBinding binding,
- object? anchor = null)
- {
- target = target ?? throw new ArgumentNullException(nameof(target));
- property = property ?? throw new ArgumentNullException(nameof(property));
- binding = binding ?? throw new ArgumentNullException(nameof(binding));
-
- return target.Bind(property, binding);
- }
-
///
/// Gets a value.
///
@@ -359,7 +332,7 @@ namespace Avalonia
return observable.Subscribe(new ClassHandlerObserver(action));
}
- private class BindingAdaptor : IBinding2
+ private class BindingAdaptor : BindingBase
{
private readonly IObservable _source;
@@ -368,17 +341,10 @@ namespace Avalonia
this._source = source;
}
- public InstancedBinding? Initiate(
+ internal override BindingExpressionBase CreateInstance(
AvaloniaObject target,
- AvaloniaProperty? targetProperty,
- object? anchor = null,
- bool enableDataValidation = false)
- {
- var expression = new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
- return new InstancedBinding(expression, BindingMode.OneWay, BindingPriority.LocalValue);
- }
-
- BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
+ AvaloniaProperty? property,
+ object? anchor)
{
return new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
}
diff --git a/src/Avalonia.Base/ClassBindingManager.cs b/src/Avalonia.Base/ClassBindingManager.cs
index 35b5bc8863..51dac66bb0 100644
--- a/src/Avalonia.Base/ClassBindingManager.cs
+++ b/src/Avalonia.Base/ClassBindingManager.cs
@@ -12,7 +12,7 @@ namespace Avalonia
private static readonly Dictionary s_RegisteredProperties =
new Dictionary();
- public static IDisposable Bind(StyledElement target, string className, IBinding source, object anchor)
+ public static IDisposable Bind(StyledElement target, string className, BindingBase source, object anchor)
{
var prop = GetClassProperty(className);
return target.Bind(prop, source);
diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
index 12219c599f..1a38a5f910 100644
--- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
@@ -59,10 +59,10 @@ namespace Avalonia.Collections
///
public event PropertyChangedEventHandler? PropertyChanged;
- ///
+ ///
public int Count => _inner.Count;
- ///
+ ///
public bool IsReadOnly => false;
///
@@ -131,7 +131,7 @@ namespace Avalonia.Collections
NotifyAdd(key, value);
}
- ///
+ ///
public void Clear()
{
var old = _inner;
@@ -152,7 +152,7 @@ namespace Avalonia.Collections
}
}
- ///
+ ///
public bool ContainsKey(TKey key) => _inner.ContainsKey(key);
///
@@ -189,8 +189,9 @@ namespace Avalonia.Collections
}
}
- ///
+ ///
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => _inner.TryGetValue(key, out value);
+
///
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
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.Base/Controls/IResourceHost.cs b/src/Avalonia.Base/Controls/IResourceHost.cs
index 361e3562ec..286f0e36ef 100644
--- a/src/Avalonia.Base/Controls/IResourceHost.cs
+++ b/src/Avalonia.Base/Controls/IResourceHost.cs
@@ -29,12 +29,4 @@ namespace Avalonia.Controls
///
void NotifyHostedResourcesChanged(ResourcesChangedEventArgs e);
}
-
- // TODO12: merge with IResourceHost
- internal interface IResourceHost2 : IResourceHost
- {
- event EventHandler ResourcesChanged2;
-
- void NotifyHostedResourcesChanged(ResourcesChangedToken token);
- }
}
diff --git a/src/Avalonia.Base/Controls/NameScope.cs b/src/Avalonia.Base/Controls/NameScope.cs
index e373f7e634..f4154525df 100644
--- a/src/Avalonia.Base/Controls/NameScope.cs
+++ b/src/Avalonia.Base/Controls/NameScope.cs
@@ -14,8 +14,8 @@ namespace Avalonia.Controls
///
/// Defines the NameScope attached property.
///
- public static readonly AttachedProperty NameScopeProperty =
- AvaloniaProperty.RegisterAttached("NameScope");
+ public static readonly AttachedProperty NameScopeProperty =
+ AvaloniaProperty.RegisterAttached("NameScope");
///
public bool IsCompleted { get; private set; }
@@ -30,7 +30,7 @@ namespace Avalonia.Controls
///
/// The styled element.
/// The value of the NameScope attached property.
- public static INameScope GetNameScope(StyledElement styled)
+ public static INameScope? GetNameScope(StyledElement styled)
{
_ = styled ?? throw new ArgumentNullException(nameof(styled));
@@ -42,7 +42,7 @@ namespace Avalonia.Controls
///
/// The styled element.
/// The value to set.
- public static void SetNameScope(StyledElement styled, INameScope value)
+ public static void SetNameScope(StyledElement styled, INameScope? value)
{
_ = styled ?? throw new ArgumentNullException(nameof(styled));
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/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs
index e0944a5708..f8f6267e7b 100644
--- a/src/Avalonia.Base/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs
@@ -358,7 +358,7 @@ namespace Avalonia.Controls
if (hasResources)
{
- owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
+ owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}
@@ -385,7 +385,7 @@ namespace Avalonia.Controls
if (hasResources)
{
- owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
+ owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}
diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
index 36cc00d245..07c1c8d654 100644
--- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
+++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
@@ -158,7 +158,7 @@ namespace Avalonia.Controls
protected override void Initialize()
{
- _target.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
+ _target.ResourcesChanged += ResourcesChanged;
if (_target is IThemeVariantHost themeVariantHost)
{
@@ -168,7 +168,7 @@ namespace Avalonia.Controls
protected override void Deinitialize()
{
- _target.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
+ _target.ResourcesChanged -= ResourcesChanged;
if (_target is IThemeVariantHost themeVariantHost)
{
@@ -186,11 +186,6 @@ namespace Avalonia.Controls
PublishNext(GetValue());
}
- private void ResourcesChanged2(object? sender, ResourcesChangedToken token)
- {
- PublishNext(GetValue());
- }
-
private void ActualThemeVariantChanged(object? sender, EventArgs e)
{
PublishNext(GetValue());
@@ -230,7 +225,7 @@ namespace Avalonia.Controls
_target.OwnerChanged += OwnerChanged;
_owner = _target.Owner;
- _owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
+ _owner?.ResourcesChanged += ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
@@ -242,7 +237,7 @@ namespace Avalonia.Controls
{
_target.OwnerChanged -= OwnerChanged;
- _owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
+ _owner?.ResourcesChanged -= ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
@@ -270,7 +265,7 @@ namespace Avalonia.Controls
private void OwnerChanged(object? sender, EventArgs e)
{
- _owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
+ _owner?.ResourcesChanged -= ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
@@ -279,7 +274,7 @@ namespace Avalonia.Controls
_owner = _target.Owner;
- _owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
+ _owner?.ResourcesChanged += ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2)
{
@@ -299,11 +294,6 @@ namespace Avalonia.Controls
PublishNext();
}
- private void ResourcesChanged2(object? sender, ResourcesChangedToken token)
- {
- PublishNext();
- }
-
private object? GetValue()
{
var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant;
diff --git a/src/Avalonia.Base/Controls/ResourceProvider.cs b/src/Avalonia.Base/Controls/ResourceProvider.cs
index f0ab42a4a8..f73f11e713 100644
--- a/src/Avalonia.Base/Controls/ResourceProvider.cs
+++ b/src/Avalonia.Base/Controls/ResourceProvider.cs
@@ -45,7 +45,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider
protected void RaiseResourcesChanged()
{
- Owner?.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
+ Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
///
@@ -57,7 +57,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider
{
if (HasResources)
{
- owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
+ owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}
@@ -70,7 +70,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider
{
if (HasResources)
{
- owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
+ owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}
diff --git a/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs b/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs
index a60c95a2e0..f78910b003 100644
--- a/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs
+++ b/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs
@@ -1,10 +1,24 @@
-using System;
+using System.Threading;
-namespace Avalonia.Controls
+namespace Avalonia.Controls;
+
+///
+/// Represents the event arguments of .
+/// The identifies the changes.
+///
+/// The sequence number used to identify the changes.
+///
+/// For performance reasons, this type is a struct.
+/// Avoid using a default instance of this type or its default constructor, call instead.
+///
+public readonly record struct ResourcesChangedEventArgs(int SequenceNumber)
{
- // TODO12: change this to be a struct, remove ResourcesChangedToken
- public class ResourcesChangedEventArgs : EventArgs
- {
- public static new readonly ResourcesChangedEventArgs Empty = new ResourcesChangedEventArgs();
- }
+ private static int s_lastSequenceNumber;
+
+ ///
+ /// Creates a new instance of with an auto-incremented sequence number.
+ ///
+ ///
+ public static ResourcesChangedEventArgs Create()
+ => new(Interlocked.Increment(ref s_lastSequenceNumber));
}
diff --git a/src/Avalonia.Base/Controls/ResourcesChangedHelper.cs b/src/Avalonia.Base/Controls/ResourcesChangedHelper.cs
deleted file mode 100644
index 8ad121ad3a..0000000000
--- a/src/Avalonia.Base/Controls/ResourcesChangedHelper.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using Avalonia.LogicalTree;
-
-namespace Avalonia.Controls;
-
-internal static class ResourcesChangedHelper
-{
- internal static void NotifyHostedResourcesChanged(this IResourceHost host, ResourcesChangedToken token)
- {
- if (host is IResourceHost2 host2)
- host2.NotifyHostedResourcesChanged(token);
- else
- host.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
- }
-
- internal static void NotifyResourcesChanged(this ILogical logical, ResourcesChangedToken token)
- {
- if (logical is StyledElement styledElement)
- styledElement.NotifyResourcesChanged(token);
- else
- logical.NotifyResourcesChanged(ResourcesChangedEventArgs.Empty);
- }
-
- internal static void SubscribeToResourcesChanged(
- this IResourceHost host,
- EventHandler handler,
- EventHandler handler2)
- {
- if (host is IResourceHost2 host2)
- host2.ResourcesChanged2 += handler2;
- else
- host.ResourcesChanged += handler;
- }
-
- internal static void UnsubscribeFromResourcesChanged(
- this IResourceHost host,
- EventHandler handler,
- EventHandler handler2)
- {
- if (host is IResourceHost2 host2)
- host2.ResourcesChanged2 -= handler2;
- else
- host.ResourcesChanged -= handler;
- }
-}
diff --git a/src/Avalonia.Base/Controls/ResourcesChangedToken.cs b/src/Avalonia.Base/Controls/ResourcesChangedToken.cs
deleted file mode 100644
index 8de692e729..0000000000
--- a/src/Avalonia.Base/Controls/ResourcesChangedToken.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Threading;
-
-namespace Avalonia.Controls;
-
-internal record struct ResourcesChangedToken(int SequenceNumber)
-{
- private static int s_lastSequenceNumber;
-
- public static ResourcesChangedToken Create()
- => new(Interlocked.Increment(ref s_lastSequenceNumber));
-}
diff --git a/src/Avalonia.Base/Data/BindingBase.cs b/src/Avalonia.Base/Data/BindingBase.cs
new file mode 100644
index 0000000000..c52ab34e88
--- /dev/null
+++ b/src/Avalonia.Base/Data/BindingBase.cs
@@ -0,0 +1,28 @@
+namespace Avalonia.Data;
+
+///
+/// Base class for the various types of binding supported by Avalonia.
+///
+public abstract class BindingBase
+{
+ ///
+ /// Creates a from a binding.
+ ///
+ /// The target of the binding.
+ /// The target property of the binding.
+ ///
+ /// If is not a control, provides an anchor object from which to
+ /// locate a data context or other controls.
+ ///
+ ///
+ /// A newly instantiated .
+ ///
+ ///
+ /// This is a low-level method which returns a binding expression that is not yet connected to
+ /// a binding sink, and so is inactive.
+ ///
+ internal abstract BindingExpressionBase CreateInstance(
+ AvaloniaObject target,
+ AvaloniaProperty? targetProperty,
+ object? anchor);
+}
diff --git a/src/Avalonia.Base/Data/BindingExpressionBase.cs b/src/Avalonia.Base/Data/BindingExpressionBase.cs
index 616f64d9d3..7e9ad9c370 100644
--- a/src/Avalonia.Base/Data/BindingExpressionBase.cs
+++ b/src/Avalonia.Base/Data/BindingExpressionBase.cs
@@ -10,8 +10,6 @@ public abstract class BindingExpressionBase : IDisposable, ISetterInstance
{
}
- internal BindingMode Mode { get; private protected set; }
-
public virtual void Dispose()
{
GC.SuppressFinalize(this);
diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs
index b0a97e5c00..fd28e53d4a 100644
--- a/src/Avalonia.Base/Data/BindingOperations.cs
+++ b/src/Avalonia.Base/Data/BindingOperations.cs
@@ -1,6 +1,4 @@
using System;
-using Avalonia.Diagnostics;
-using Avalonia.Reactive;
namespace Avalonia.Data
{
@@ -8,99 +6,6 @@ namespace Avalonia.Data
{
public static readonly object DoNothing = new DoNothingType();
- ///
- /// Applies an a property on an .
- ///
- /// The target object.
- /// The property to bind.
- /// The instanced binding.
- /// An which can be used to cancel the binding.
- [Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
- public static IDisposable Apply(
- AvaloniaObject target,
- AvaloniaProperty property,
- InstancedBinding binding)
- {
- _ = target ?? throw new ArgumentNullException(nameof(target));
- _ = property ?? throw new ArgumentNullException(nameof(property));
- _ = binding ?? throw new ArgumentNullException(nameof(binding));
-
- if (binding.Expression is { } expression)
- {
- return target.GetValueStore().AddBinding(property, expression);
- }
-
- var mode = binding.Mode;
-
- if (mode == BindingMode.Default)
- {
- mode = property.GetMetadata(target).DefaultBindingMode;
- }
-
- switch (mode)
- {
- case BindingMode.Default:
- case BindingMode.OneWay:
- return target.Bind(property, binding.Source, binding.Priority);
- case BindingMode.TwoWay:
- {
- if (binding.Source is not IObserver observer)
- throw new InvalidOperationException("InstancedBinding does not contain a subject.");
- return new TwoWayBindingDisposable(
- target.Bind(property, binding.Source, binding.Priority),
- target.GetObservable(property).Subscribe(observer));
- }
- case BindingMode.OneTime:
- {
- // Perf: Avoid allocating closure in the outer scope.
- var targetCopy = target;
- var propertyCopy = property;
- var bindingCopy = binding;
-
- return binding.Source
- .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
- .Take(1)
- .Subscribe(x => targetCopy.SetValue(
- propertyCopy,
- BindingNotification.ExtractValue(x),
- bindingCopy.Priority));
- }
-
- case BindingMode.OneWayToSource:
- {
- if (binding.Source is not IObserver observer)
- throw new InvalidOperationException("InstancedBinding does not contain a subject.");
-
- return Observable.CombineLatest(
- binding.Source,
- target.GetObservable(property),
- (_, v) => v)
- .Subscribe(x => observer.OnNext(x));
- }
-
- default:
- throw new ArgumentException("Invalid binding mode.");
- }
- }
-
- ///
- /// Applies an a property on an .
- ///
- /// The target object.
- /// The property to bind.
- /// The instanced binding.
- /// Obsolete, unused.
- /// An which can be used to cancel the binding.
- [Obsolete("Use the Apply(AvaloniaObject, AvaloniaProperty, InstancedBinding) overload.")]
- public static IDisposable Apply(
- AvaloniaObject target,
- AvaloniaProperty property,
- InstancedBinding binding,
- object? anchor)
- {
- return Apply(target, property, binding);
- }
-
///
/// Retrieves the that is currently active on the
/// specified property.
@@ -119,33 +24,6 @@ namespace Avalonia.Data
{
return target.GetValueStore().GetExpression(property);
}
-
- private sealed class TwoWayBindingDisposable : IDisposable
- {
- private readonly IDisposable _toTargetSubscription;
- private readonly IDisposable _fromTargetSubsription;
-
- private bool _isDisposed;
-
- public TwoWayBindingDisposable(IDisposable toTargetSubscription, IDisposable fromTargetSubsription)
- {
- _toTargetSubscription = toTargetSubscription;
- _fromTargetSubsription = fromTargetSubsription;
- }
-
- public void Dispose()
- {
- if (_isDisposed)
- {
- return;
- }
-
- _fromTargetSubsription.Dispose();
- _toTargetSubscription.Dispose();
-
- _isDisposed = true;
- }
- }
}
public sealed class DoNothingType
diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs
index cb7f559e0a..b817c89f7b 100644
--- a/src/Avalonia.Base/Data/BindingPriority.cs
+++ b/src/Avalonia.Base/Data/BindingPriority.cs
@@ -46,9 +46,6 @@ namespace Avalonia.Data
///
/// The value is uninitialized.
///
- Unset = int.MaxValue,
-
- [Obsolete("Use Template priority"), EditorBrowsable(EditorBrowsableState.Never)]
- TemplatedParent = Template,
+ Unset = int.MaxValue
}
}
diff --git a/src/Avalonia.Base/Data/CompiledBinding.cs b/src/Avalonia.Base/Data/CompiledBinding.cs
new file mode 100644
index 0000000000..e243246b4f
--- /dev/null
+++ b/src/Avalonia.Base/Data/CompiledBinding.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq.Expressions;
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.ExpressionNodes;
+using Avalonia.Data.Core.Parsers;
+using Avalonia.Metadata;
+
+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;
+
+ ///
+ /// Creates a from a lambda expression.
+ ///
+ /// The input type of the binding expression.
+ /// The output type of the binding expression.
+ ///
+ /// The lambda expression representing the binding path
+ /// (e.g., vm => vm.PropertyName).
+ ///
+ /// The source object for the binding. If null, uses the target's DataContext.
+ ///
+ ///
+ /// Optional value converter to transform values between source and target.
+ ///
+ ///
+ /// The binding mode. Default is which resolves to the
+ /// property's default binding mode.
+ ///
+ /// The binding priority.
+ /// The culture in which to evaluate the converter.
+ /// A parameter to pass to the converter.
+ ///
+ /// The value to use when the binding is unable to produce a value.
+ ///
+ /// The string format for the binding result.
+ /// The value to use when the binding result is null.
+ ///
+ /// The timing of binding source updates for TwoWay/OneWayToSource bindings.
+ ///
+ ///
+ /// The amount of time, in milliseconds, to wait before updating the binding source.
+ ///
+ ///
+ /// A configured instance ready to be applied to a property.
+ ///
+ ///
+ /// Thrown when the expression contains unsupported operations or invalid syntax for binding
+ /// expressions.
+ ///
+ ///
+ /// This builds a with a path described by a lambda expression.
+ /// The resulting binding avoids reflection for property access, providing better performance
+ /// than reflection-based bindings.
+ ///
+ /// Supported expressions include:
+ ///
+ /// - Property access: x => x.Property
+ /// - Nested properties: x => x.Property.Nested
+ /// - Indexers: x => x.Items[0]
+ /// - Type casts: x => ((DerivedType)x).Property
+ /// - Logical NOT: x => !x.BoolProperty
+ /// - Stream bindings: x => x.TaskProperty (Task/Observable)
+ /// - AvaloniaProperty access: x => x[MyProperty]
+ ///
+ ///
+ [RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)]
+ [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
+ public static CompiledBinding Create(
+ Expression> expression,
+ object? source = null,
+ IValueConverter? converter = null,
+ BindingMode mode = BindingMode.Default,
+ BindingPriority priority = BindingPriority.LocalValue,
+ CultureInfo? converterCulture = null,
+ object? converterParameter = null,
+ object? fallbackValue = null,
+ string? stringFormat = null,
+ object? targetNullValue = null,
+ UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.Default,
+ int delay = 0)
+ {
+ var path = BindingExpressionVisitor.BuildPath(expression);
+ return new CompiledBinding(path)
+ {
+ Source = source ?? AvaloniaProperty.UnsetValue,
+ Converter = converter,
+ ConverterCulture = converterCulture,
+ ConverterParameter = converterParameter,
+ FallbackValue = fallbackValue ?? AvaloniaProperty.UnsetValue,
+ Mode = mode,
+ Priority = priority,
+ StringFormat = stringFormat,
+ TargetNullValue = targetNullValue ?? AvaloniaProperty.UnsetValue,
+ UpdateSourceTrigger = updateSourceTrigger,
+ Delay = delay
+ };
+ }
+
+ ///
+ /// 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.
+ ///
+ [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 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 90%
rename from src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
rename to src/Avalonia.Base/Data/CompiledBindingPath.cs
index 89671708b3..886d89df43 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
{
@@ -94,26 +94,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal IEnumerable Elements => _elements;
- internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
- ? SourceMode.Control : SourceMode.Data;
-
///
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()
{
@@ -123,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(
@@ -225,12 +203,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
- [Obsolete("This method doesn't do anything anymore. Use Binding.Source instead.")]
- public CompiledBindingPathBuilder SetRawSource(object? rawSource)
- {
- return this;
- }
-
public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray());
}
@@ -288,7 +260,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/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs
index a51ee5bf8a..00a90f87fe 100644
--- a/src/Avalonia.Base/Data/Core/BindingExpression.cs
+++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Data.Core;
/// A represents a untyped binding which has been
/// instantiated on an object.
///
-internal partial class BindingExpression : UntypedBindingExpressionBase, IDescription, IDisposable
+internal class BindingExpression : UntypedBindingExpressionBase, IDescription, IDisposable
{
private static readonly List s_emptyExpressionNodes = new();
private readonly WeakReference? _source;
@@ -138,7 +138,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
get
{
var b = new StringBuilder();
- LeafNode.BuildString(b, _nodes);
+ LeafNode?.BuildString(b, _nodes);
return b.ToString();
}
}
@@ -149,7 +149,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
public CultureInfo ConverterCulture => _uncommon?._converterCulture ?? CultureInfo.CurrentCulture;
public object? ConverterParameter => _uncommon?._converterParameter;
public object? FallbackValue => _uncommon is not null ? _uncommon._fallbackValue : AvaloniaProperty.UnsetValue;
- public ExpressionNode LeafNode => _nodes[_nodes.Count - 1];
+ public ExpressionNode? LeafNode => _nodes.Count > 0 ? _nodes[_nodes.Count - 1] : null;
public string? StringFormat => _uncommon?._stringFormat;
public object? TargetNullValue => _uncommon?._targetNullValue ?? AvaloniaProperty.UnsetValue;
public UpdateSourceTrigger UpdateSourceTrigger => _uncommon?._updateSourceTrigger ?? UpdateSourceTrigger.PropertyChanged;
@@ -173,59 +173,6 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
_nodes[0].SetSource(source, null);
}
- ///
- /// Creates an from an expression tree.
- ///
- /// The input type of the binding expression.
- /// The output type of the binding expression.
- /// The source from which the binding value will be read.
- /// The expression representing the binding path.
- /// The converter to use.
- /// The converter culture to use.
- /// The converter parameter.
- /// Whether data validation should be enabled for the binding.
- /// The fallback value.
- /// The binding mode.
- /// The binding priority.
- /// The null target value.
- /// Whether to allow reflection for target type conversion.
- [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
-#if NET8_0_OR_GREATER
- [RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)]
-#endif
- internal static BindingExpression Create(
- TIn source,
- Expression> expression,
- IValueConverter? converter = null,
- CultureInfo? converterCulture = null,
- object? converterParameter = null,
- bool enableDataValidation = false,
- Optional fallbackValue = default,
- BindingMode mode = BindingMode.OneWay,
- BindingPriority priority = BindingPriority.LocalValue,
- object? targetNullValue = null,
- bool allowReflection = true)
- where TIn : class?
- {
- var nodes = BindingExpressionVisitor.BuildNodes(expression, enableDataValidation);
- var fallback = fallbackValue.HasValue ? fallbackValue.Value : AvaloniaProperty.UnsetValue;
-
- return new BindingExpression(
- source,
- nodes,
- fallback,
- converter: converter,
- converterCulture: converterCulture,
- converterParameter: converterParameter,
- enableDataValidation: enableDataValidation,
- mode: mode,
- priority: priority,
- targetNullValue: targetNullValue,
- targetTypeConverter: allowReflection ?
- TargetTypeConverter.GetReflectionConverter() :
- TargetTypeConverter.GetDefaultConverter());
- }
-
///
/// Called by an belonging to this binding when its
/// changes.
@@ -360,7 +307,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
// Don't set the value if it's unchanged. If there is a binding error, we still have to set the value
// in order to clear the error.
- if (TypeUtilities.IdentityEquals(LeafNode.Value, value, type) && ErrorType == BindingErrorType.None)
+ if (TypeUtilities.IdentityEquals(LeafNode!.Value, value, type) && ErrorType == BindingErrorType.None)
return true;
try
@@ -515,7 +462,8 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
if (TryGetTarget(out var target) &&
TargetProperty is not null &&
target.GetValue(TargetProperty) is var value &&
- !TypeUtilities.IdentityEquals(value, LeafNode.Value, TargetType))
+ LeafNode is { } leafNode &&
+ !TypeUtilities.IdentityEquals(value, leafNode.Value, TargetType))
{
WriteValueToSource(value);
}
diff --git a/src/Avalonia.Base/Data/Core/IBinding2.cs b/src/Avalonia.Base/Data/Core/IBinding2.cs
deleted file mode 100644
index 1dcbc15b0c..0000000000
--- a/src/Avalonia.Base/Data/Core/IBinding2.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Avalonia.Data.Core;
-
-///
-/// Internal interface for instancing bindings on an .
-///
-///
-/// TODO12: The presence of this interface is a hack needed because we can't break our API until
-/// 12.0. The Instance method would ideally be located as an internal method on a BindingBase
-/// class, but we already have a BindingBase in 11.x which is not suitable for this as it contains
-/// extra members that are not needed on all of the binding types. The current BindingBase should
-/// be renamed to something like BindingMarkupExtensionBase and a new BindingBase created with the
-/// Instance method from this interface. This interface should then be removed.
-///
-internal interface IBinding2 : IBinding
-{
- BindingExpressionBase Instance(
- AvaloniaObject target,
- AvaloniaProperty? targetProperty,
- object? anchor);
-}
diff --git a/src/Avalonia.Base/Data/Core/MultiBindingExpression.cs b/src/Avalonia.Base/Data/Core/MultiBindingExpression.cs
index 4d0a612a7e..2e9cb0b9cf 100644
--- a/src/Avalonia.Base/Data/Core/MultiBindingExpression.cs
+++ b/src/Avalonia.Base/Data/Core/MultiBindingExpression.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Data.Core;
internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingExpressionSink
{
private static readonly object s_uninitialized = new object();
- private readonly IBinding[] _bindings;
+ private readonly BindingBase[] _bindings;
private readonly IMultiValueConverter? _converter;
private readonly CultureInfo? _converterCulture;
private readonly object? _converterParameter;
@@ -22,7 +22,7 @@ internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingEx
public MultiBindingExpression(
BindingPriority priority,
- IList bindings,
+ IList bindings,
IMultiValueConverter? converter,
CultureInfo? converterCulture,
object? converterParameter,
@@ -63,12 +63,7 @@ internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingEx
for (var i = 0; i < _bindings.Length; ++i)
{
- var binding = _bindings[i];
-
- if (binding is not IBinding2 b)
- throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'.");
-
- var expression = b.Instance(target, null, null);
+ var expression = _bindings[i].CreateInstance(target, null, null);
if (expression is not UntypedBindingExpressionBase e)
throw new NotSupportedException($"Unsupported BindingExpressionBase implementation '{expression}'.");
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs
similarity index 96%
rename from src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
rename to src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs
index c3651594e9..a3ffdb4aa6 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
+++ b/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs
@@ -1,8 +1,7 @@
using System.Collections.Generic;
-using Avalonia.Data.Core;
using Avalonia.Utilities;
-namespace Avalonia.Markup.Parsers
+namespace Avalonia.Data.Core.Parsers
{
internal static class ArgumentListParser
{
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionGrammar.cs
similarity index 99%
rename from src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
rename to src/Avalonia.Base/Data/Core/Parsers/BindingExpressionGrammar.cs
index 97ae40931e..c49468f1cd 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
+++ b/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionGrammar.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using Avalonia.Data.Core;
using Avalonia.Utilities;
-namespace Avalonia.Markup.Parsers
+namespace Avalonia.Data.Core.Parsers
{
internal enum SourceMode
{
diff --git a/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs b/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs
index 1981732145..c354bcc15a 100644
--- a/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs
+++ b/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs
@@ -1,47 +1,59 @@
using System;
-using System.Collections;
using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
+using System.Threading.Tasks;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.ExpressionNodes.Reflection;
+using Avalonia.Data.Core.Plugins;
+using Avalonia.Reactive;
+using Avalonia.Utilities;
namespace Avalonia.Data.Core.Parsers;
-[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
-#if NET8_0_OR_GREATER
+///
+/// Visits and processes a LINQ expression to build a compiled binding path.
+///
+/// The input parameter type for the binding expression.
+///
+/// This visitor traverses lambda expressions used in compiled bindings and uses
+/// to construct a , which
+/// can then be converted into instances. It supports property access,
+/// indexers, AvaloniaProperty access, stream bindings, type casts, and logical operators.
+///
[RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)]
-#endif
-internal class BindingExpressionVisitor : ExpressionVisitor
+[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
+internal class BindingExpressionVisitor(LambdaExpression expression) : ExpressionVisitor
{
- private static readonly PropertyInfo AvaloniaObjectIndexer;
- private static readonly MethodInfo CreateDelegateMethod;
private const string IndexerGetterName = "get_Item";
private const string MultiDimensionalArrayGetterMethodName = "Get";
- private readonly bool _enableDataValidation;
- private readonly LambdaExpression _rootExpression;
- private readonly List _nodes = new();
+ private readonly LambdaExpression _rootExpression = expression;
+ private readonly CompiledBindingPathBuilder _builder = new();
private Expression? _head;
- public BindingExpressionVisitor(LambdaExpression expression, bool enableDataValidation)
- {
- _rootExpression = expression;
- _enableDataValidation = enableDataValidation;
- }
-
- static BindingExpressionVisitor()
- {
- AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty("Item", new[] { typeof(AvaloniaProperty) })!;
- CreateDelegateMethod = typeof(MethodInfo).GetMethod("CreateDelegate", new[] { typeof(Type), typeof(object) })!;
- }
-
- public static List BuildNodes(Expression> expression, bool enableDataValidation)
+ ///
+ /// Builds a compiled binding path from a lambda expression.
+ ///
+ /// The output type of the binding expression.
+ ///
+ /// The lambda expression to parse and convert into a binding path.
+ ///
+ ///
+ /// A representing the binding path.
+ ///
+ ///
+ /// Thrown when the expression contains unsupported operations or invalid syntax for binding
+ /// expressions.
+ ///
+ public static CompiledBindingPath BuildPath(Expression> expression)
{
- var visitor = new BindingExpressionVisitor(expression, enableDataValidation);
+ var visitor = new BindingExpressionVisitor(expression);
visitor.Visit(expression);
- return visitor._nodes;
+ return visitor._builder.Build();
}
protected override Expression VisitBinary(BinaryExpression node)
@@ -49,33 +61,64 @@ internal class BindingExpressionVisitor : ExpressionVisitor
// Indexers require more work since the compiler doesn't generate IndexExpressions:
// they weren't in System.Linq.Expressions v1 and so must be generated manually.
if (node.NodeType == ExpressionType.ArrayIndex)
- return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right }));
+ return Visit(Expression.MakeIndex(node.Left, null, [node.Right]));
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}
protected override Expression VisitIndex(IndexExpression node)
{
- if (node.Indexer == AvaloniaObjectIndexer)
+ if (node.Indexer == BindingExpressionVisitorMembers.AvaloniaObjectIndexer)
{
var property = GetValue(node.Arguments[0]);
- return Add(node.Object, node, new AvaloniaPropertyAccessorNode(property));
+ return Add(node.Object, node, x => x.Property(property, CreateAvaloniaPropertyAccessor));
}
- else
+ else if (node.Object?.Type.IsArray == true)
+ {
+ var indexes = node.Arguments.Select(GetValue).ToArray();
+ return Add(node.Object, node, x => x.ArrayElement(indexes, node.Type));
+ }
+ else if (node.Indexer?.GetMethod is not null &&
+ node.Arguments.Count == 1 &&
+ node.Arguments[0].Type == typeof(int))
{
- return Add(node.Object, node, new ExpressionTreeIndexerNode(node));
+ var getMethod = node.Indexer.GetMethod;
+ var setMethod = node.Indexer.SetMethod;
+ var index = GetValue(node.Arguments[0]);
+ var info = new ClrPropertyInfo(
+ CommonPropertyNames.IndexerName,
+ x => getMethod.Invoke(x, new object[] { index }),
+ setMethod is not null ? (o, v) => setMethod.Invoke(o, new[] { index, v }) : null,
+ getMethod.ReturnType);
+ return Add(node.Object, node, x => x.Property(
+ info,
+ (weakRef, propInfo) => CreateIndexerPropertyAccessor(weakRef, propInfo, index)));
}
+ else if (node.Indexer?.GetMethod is not null)
+ {
+ var getMethod = node.Indexer.GetMethod;
+ var setMethod = node.Indexer?.SetMethod;
+ var indexes = node.Arguments.Select(GetValue).ToArray();
+ var info = new ClrPropertyInfo(
+ CommonPropertyNames.IndexerName,
+ x => getMethod.Invoke(x, indexes),
+ setMethod is not null ? (o, v) => setMethod.Invoke(o, indexes.Append(v).ToArray()) : null,
+ getMethod.ReturnType);
+ return Add(node.Object, node, x => x.Property(
+ info,
+ CreateInpcPropertyAccessor));
+ }
+
+ throw new ExpressionParseException(0, $"Invalid indexer in binding expression: {node.NodeType}.");
}
protected override Expression VisitMember(MemberExpression node)
{
- switch (node.Member.MemberType)
+ return node.Member.MemberType switch
{
- case MemberTypes.Property:
- return Add(node.Expression, node, new DynamicPluginPropertyAccessorNode(node.Member.Name, acceptsNull: false));
- default:
- throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
- }
+ MemberTypes.Property => AddPropertyNode(node),
+ _ => throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."),
+ };
}
protected override Expression VisitMethodCall(MethodCallExpression node)
@@ -90,20 +133,43 @@ internal class BindingExpressionVisitor : ExpressionVisitor
else if (method.Name == MultiDimensionalArrayGetterMethodName &&
node.Object is not null)
{
- var expression = Expression.MakeIndex(node.Object, null, node.Arguments);
- return Add(node.Object, node, new ExpressionTreeIndexerNode(expression));
+ var indexes = node.Arguments.Select(GetValue).ToArray();
+ return Add(node.Object, node, x => x.ArrayElement(indexes, node.Type));
}
else if (method.Name.StartsWith(StreamBindingExtensions.StreamBindingName) &&
method.DeclaringType == typeof(StreamBindingExtensions))
{
var instance = node.Method.IsStatic ? node.Arguments[0] : node.Object;
- Add(instance, node, new DynamicPluginStreamNode());
- return node;
+ var instanceType = instance?.Type;
+ var genericArgs = method.GetGenericArguments();
+ var genericArg = genericArgs.Length > 0 ? genericArgs[0] : typeof(object);
+
+ if (instanceType == typeof(Task) ||
+ (instanceType?.IsGenericType == true &&
+ instanceType.GetGenericTypeDefinition() == typeof(Task<>) &&
+ genericArg.IsAssignableFrom(instanceType.GetGenericArguments()[0])))
+ {
+ var builderMethod = typeof(CompiledBindingPathBuilder)
+ .GetMethod(nameof(CompiledBindingPathBuilder.StreamTask))!
+ .MakeGenericMethod(genericArg);
+ return Add(instance, node, x => builderMethod.Invoke(x, null));
+ }
+ else if (typeof(IObservable<>).MakeGenericType(genericArg).IsAssignableFrom(instance?.Type))
+ {
+ var builderMethod = typeof(CompiledBindingPathBuilder)
+ .GetMethod(nameof(CompiledBindingPathBuilder.StreamObservable))!
+ .MakeGenericMethod(genericArg);
+ return Add(instance, node, x => builderMethod.Invoke(x, null));
+ }
}
- else if (method == CreateDelegateMethod)
+ else if (method == BindingExpressionVisitorMembers.CreateDelegateMethod)
{
- var accessor = new DynamicPluginPropertyAccessorNode(GetValue(node.Object!).Name, acceptsNull: false);
- return Add(node.Arguments[1], node, accessor);
+ var methodInfo = GetValue(node.Object!);
+ var delegateType = GetValue(node.Arguments[0]);
+ return Add(node.Arguments[1], node, x => x.Method(
+ methodInfo.MethodHandle,
+ delegateType.TypeHandle,
+ acceptsNull: false));
}
throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType}.{node.Method.Name}'.");
@@ -120,20 +186,26 @@ internal class BindingExpressionVisitor : ExpressionVisitor
{
if (node.NodeType == ExpressionType.Not && node.Type == typeof(bool))
{
- return Add(node.Operand, node, new LogicalNotNode());
+ return Add(node.Operand, node, x => x.Not());
}
else if (node.NodeType == ExpressionType.Convert)
{
- if (node.Operand.Type.IsAssignableFrom(node.Type))
+ // Allow reference type casts (both upcasts and downcasts) but reject value type conversions
+ if (!node.Type.IsValueType && !node.Operand.Type.IsValueType &&
+ (node.Type.IsAssignableFrom(node.Operand.Type) || node.Operand.Type.IsAssignableFrom(node.Type)))
{
- // Ignore inheritance casts
- return _head = base.VisitUnary(node);
+ var castMethod = typeof(CompiledBindingPathBuilder)
+ .GetMethod(nameof(CompiledBindingPathBuilder.TypeCast))!
+ .MakeGenericMethod(node.Type);
+ return Add(node.Operand, node, x => castMethod.Invoke(x, null));
}
}
else if (node.NodeType == ExpressionType.TypeAs)
{
- // Ignore as operator.
- return _head = base.VisitUnary(node);
+ var castMethod = typeof(CompiledBindingPathBuilder)
+ .GetMethod(nameof(CompiledBindingPathBuilder.TypeCast))!
+ .MakeGenericMethod(node.Type);
+ return Add(node.Operand, node, x => castMethod.Invoke(x, null));
}
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
@@ -146,7 +218,7 @@ internal class BindingExpressionVisitor : ExpressionVisitor
protected override CatchBlock VisitCatchBlock(CatchBlock node)
{
- throw new ExpressionParseException(0, $"Catch blocks are not allowed in binding expressions.");
+ throw new ExpressionParseException(0, "Catch blocks are not allowed in binding expressions.");
}
protected override Expression VisitConditional(ConditionalExpression node)
@@ -156,17 +228,17 @@ internal class BindingExpressionVisitor : ExpressionVisitor
protected override Expression VisitDynamic(DynamicExpression node)
{
- throw new ExpressionParseException(0, $"Dynamic expressions are not allowed in binding expressions.");
+ throw new ExpressionParseException(0, "Dynamic expressions are not allowed in binding expressions.");
}
protected override ElementInit VisitElementInit(ElementInit node)
{
- throw new ExpressionParseException(0, $"Element init expressions are not valid in a binding expression.");
+ throw new ExpressionParseException(0, "Element init expressions are not valid in a binding expression.");
}
protected override Expression VisitGoto(GotoExpression node)
{
- throw new ExpressionParseException(0, $"Goto expressions not supported in binding expressions.");
+ throw new ExpressionParseException(0, "Goto expressions are not supported in binding expressions.");
}
protected override Expression VisitInvocation(InvocationExpression node)
@@ -191,7 +263,7 @@ internal class BindingExpressionVisitor : ExpressionVisitor
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
- throw new ExpressionParseException(0, $"Member assignments not supported in binding expressions.");
+ throw new ExpressionParseException(0, "Member assignments not supported in binding expressions.");
}
protected override Expression VisitSwitch(SwitchExpression node)
@@ -209,17 +281,69 @@ internal class BindingExpressionVisitor : ExpressionVisitor
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}
- private Expression Add(Expression? instance, Expression expression, ExpressionNode node)
+ private Expression Add(Expression? instance, Expression expression, Action build)
{
var visited = Visit(instance);
+
if (visited != _head)
+ {
throw new ExpressionParseException(
- 0,
+ 0,
$"Unable to parse '{expression}': expected an instance of '{_head}' but got '{visited}'.");
- _nodes.Add(node);
+ }
+
+ build(_builder);
return _head = expression;
}
+ private Expression AddPropertyNode(MemberExpression node)
+ {
+ // Check if it's an AvaloniaProperty accessed via CLR wrapper
+ if (typeof(AvaloniaObject).IsAssignableFrom(node.Expression?.Type) &&
+ AvaloniaPropertyRegistry.Instance.FindRegistered(node.Expression.Type, node.Member.Name) is { } avaloniaProperty)
+ {
+ return Add(
+ node.Expression,
+ node,
+ x => x.Property(avaloniaProperty, CreateAvaloniaPropertyAccessor));
+ }
+ else
+ {
+ var property = (PropertyInfo)node.Member;
+ var info = new ClrPropertyInfo(
+ property.Name,
+ CreateGetter(property),
+ CreateSetter(property),
+ property.PropertyType);
+ return Add(node.Expression, node, x => x.Property(info, CreateInpcPropertyAccessor));
+ }
+ }
+
+ private static Func? CreateGetter(PropertyInfo info)
+ {
+ if (info.GetMethod == null)
+ return null;
+ var target = Expression.Parameter(typeof(object), "target");
+ return Expression.Lambda>(
+ Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType!), info.GetMethod),
+ typeof(object)),
+ target)
+ .Compile();
+ }
+
+ private static Action? CreateSetter(PropertyInfo info)
+ {
+ if (info.SetMethod == null)
+ return null;
+ var target = Expression.Parameter(typeof(object), "target");
+ var value = Expression.Parameter(typeof(object), "value");
+ return Expression.Lambda>(
+ Expression.Call(Expression.Convert(target, info.DeclaringType!), info.SetMethod,
+ Expression.Convert(value, info.SetMethod.GetParameters()[0].ParameterType)),
+ target, value)
+ .Compile();
+ }
+
private static T GetValue(Expression expr)
{
if (expr is ConstantExpression constant)
@@ -232,4 +356,168 @@ internal class BindingExpressionVisitor : ExpressionVisitor
var type = method.DeclaringType;
return type?.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method);
}
+
+ // Accessor factory methods
+ private static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference target, IPropertyInfo property)
+ => new InpcPropertyAccessor(target, property);
+
+ private static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference target, IPropertyInfo property)
+ => new AvaloniaPropertyAccessor(
+ new WeakReference((AvaloniaObject?)(target.TryGetTarget(out var o) ? o : null)),
+ (AvaloniaProperty)property);
+
+ private static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference target, IPropertyInfo property, int argument)
+ => new IndexerAccessor(target, property, argument);
+
+ // Accessor implementations
+ private class AvaloniaPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber
+ {
+ private readonly WeakReference _reference;
+ private readonly AvaloniaProperty _property;
+
+ public AvaloniaPropertyAccessor(WeakReference reference, AvaloniaProperty property)
+ {
+ _reference = reference ?? throw new ArgumentNullException(nameof(reference));
+ _property = property ?? throw new ArgumentNullException(nameof(property));
+ }
+
+ public override Type PropertyType => _property.PropertyType;
+ public override object? Value => _reference.TryGetTarget(out var instance) ? instance?.GetValue(_property) : null;
+
+ public override bool SetValue(object? value, BindingPriority priority)
+ {
+ if (!_property.IsReadOnly && _reference.TryGetTarget(out var instance))
+ {
+ instance.SetValue(_property, value, priority);
+ return true;
+ }
+ return false;
+ }
+
+ public void OnEvent(object? sender, WeakEvent ev, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == _property)
+ PublishValue(Value);
+ }
+
+ protected override void SubscribeCore()
+ {
+ if (_reference.TryGetTarget(out var reference) && reference is not null)
+ {
+ PublishValue(reference.GetValue(_property));
+ WeakEvents.AvaloniaPropertyChanged.Subscribe(reference, this);
+ }
+ }
+
+ protected override void UnsubscribeCore()
+ {
+ if (_reference.TryGetTarget(out var reference) && reference is not null)
+ WeakEvents.AvaloniaPropertyChanged.Unsubscribe(reference, this);
+ }
+ }
+
+ private class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber
+ {
+ protected readonly WeakReference _reference;
+ private readonly IPropertyInfo _property;
+
+ public InpcPropertyAccessor(WeakReference reference, IPropertyInfo property)
+ {
+ _reference = reference ?? throw new ArgumentNullException(nameof(reference));
+ _property = property ?? throw new ArgumentNullException(nameof(property));
+ }
+
+ public override Type PropertyType => _property.PropertyType;
+ public override object? Value => _reference.TryGetTarget(out var o) ? _property.Get(o) : null;
+
+ public override bool SetValue(object? value, BindingPriority priority)
+ {
+ if (_property.CanSet && _reference.TryGetTarget(out var o))
+ {
+ _property.Set(o, value);
+ SendCurrentValue();
+ return true;
+ }
+ return false;
+ }
+
+ public void OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
+ SendCurrentValue();
+ }
+
+ protected override void SubscribeCore()
+ {
+ SendCurrentValue();
+ if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
+ WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
+ }
+
+ protected override void UnsubscribeCore()
+ {
+ if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
+ WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
+ }
+
+ protected void SendCurrentValue()
+ {
+ try
+ {
+ PublishValue(Value);
+ }
+ catch (Exception e)
+ {
+ PublishValue(new BindingNotification(e, BindingErrorType.Error));
+ }
+ }
+ }
+
+ private class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber
+ {
+ private readonly int _index;
+
+ public IndexerAccessor(WeakReference target, IPropertyInfo basePropertyInfo, int argument)
+ : base(target, basePropertyInfo)
+ {
+ _index = argument;
+ }
+
+ protected override void SubscribeCore()
+ {
+ base.SubscribeCore();
+ if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
+ WeakEvents.CollectionChanged.Subscribe(incc, this);
+ }
+
+ protected override void UnsubscribeCore()
+ {
+ base.UnsubscribeCore();
+ if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
+ WeakEvents.CollectionChanged.Unsubscribe(incc, this);
+ }
+
+ public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
+ {
+ if (ShouldNotifyListeners(args))
+ SendCurrentValue();
+ }
+
+ private bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
+ {
+ return e.Action switch
+ {
+ NotifyCollectionChangedAction.Add => _index >= e.NewStartingIndex,
+ NotifyCollectionChangedAction.Remove => _index >= e.OldStartingIndex,
+ NotifyCollectionChangedAction.Replace => _index >= e.NewStartingIndex &&
+ _index < e.NewStartingIndex + e.NewItems!.Count,
+ NotifyCollectionChangedAction.Move => (_index >= e.NewStartingIndex &&
+ _index < e.NewStartingIndex + e.NewItems!.Count) ||
+ (_index >= e.OldStartingIndex &&
+ _index < e.OldStartingIndex + e.OldItems!.Count),
+ NotifyCollectionChangedAction.Reset => true,
+ _ => false
+ };
+ }
+ }
}
diff --git a/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitorMembers.cs b/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitorMembers.cs
new file mode 100644
index 0000000000..82163f89e6
--- /dev/null
+++ b/src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitorMembers.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Reflection;
+
+namespace Avalonia.Data.Core.Parsers;
+
+///
+/// Stores reflection members used by outside of the
+/// generic class to avoid duplication for each generic instantiation.
+///
+internal static class BindingExpressionVisitorMembers
+{
+ static BindingExpressionVisitorMembers()
+ {
+ AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty(CommonPropertyNames.IndexerName, [typeof(AvaloniaProperty)])!;
+ CreateDelegateMethod = typeof(MethodInfo).GetMethod(nameof(MethodInfo.CreateDelegate), [typeof(Type), typeof(object)])!;
+ }
+
+ public static readonly PropertyInfo AvaloniaObjectIndexer;
+ public static readonly MethodInfo CreateDelegateMethod;
+}
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionNodeFactory.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionNodeFactory.cs
similarity index 95%
rename from src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionNodeFactory.cs
rename to src/Avalonia.Base/Data/Core/Parsers/ExpressionNodeFactory.cs
index 59794ceb92..f7eb2d537d 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionNodeFactory.cs
+++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionNodeFactory.cs
@@ -7,7 +7,7 @@ using Avalonia.Data;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.ExpressionNodes.Reflection;
-namespace Avalonia.Markup.Parsers
+namespace Avalonia.Data.Core.Parsers
{
///
/// Creates s from a .
@@ -20,7 +20,7 @@ namespace Avalonia.Markup.Parsers
#endif
public static List? CreateFromAst(
List astNodes,
- Func? typeResolver,
+ Func? typeResolver,
INameScope? nameScope,
out bool isRooted)
{
@@ -111,7 +111,7 @@ namespace Avalonia.Markup.Parsers
}
private static AvaloniaPropertyAccessorNode AttachedPropertyNode(
- Func? typeResolver,
+ Func? typeResolver,
BindingExpressionGrammar.AttachedPropertyNameNode attached)
{
var type = LookupType(typeResolver, attached.Namespace, attached.TypeName);
@@ -121,7 +121,7 @@ namespace Avalonia.Markup.Parsers
}
private static LogicalAncestorElementNode LogicalAncestorNode(
- Func? typeResolver,
+ Func? typeResolver,
BindingExpressionGrammar.AncestorNode ancestor)
{
Type? type = null;
@@ -135,7 +135,7 @@ namespace Avalonia.Markup.Parsers
}
private static Type LookupType(
- Func? typeResolver,
+ Func? typeResolver,
string? @namespace,
string? name)
{
diff --git a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
index 6b52bbe259..e45c88c457 100644
--- a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
+++ b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
@@ -220,11 +220,10 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
/// The binding expression is already instantiated on an AvaloniaObject.
///
///
- /// This method is mostly here for backwards compatibility with
- /// and unit testing and we may want to remove it in future. In particular its usefulness in
- /// terms of unit testing is limited in that it preserves the semantics of binding expressions
- /// as expected by unit tests, not necessarily the semantics that will be used when the
- /// expression is used as an instantiated in a
+ /// This method is mostly here for unit testing and we may want to remove it in future. In
+ /// particular its usefulness is limited in that it preserves the semantics of binding
+ /// expressions as expected by unit tests, not necessarily the semantics that will be used
+ /// when the expression is used as an instantiated in a
/// . Unit tests should be migrated to not test the behaviour of
/// binding expressions through an observable, and instead test the behaviour of the binding
/// when applied to an .
diff --git a/src/Avalonia.Base/Data/IBinding.cs b/src/Avalonia.Base/Data/IBinding.cs
deleted file mode 100644
index e000da18ff..0000000000
--- a/src/Avalonia.Base/Data/IBinding.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using Avalonia.Diagnostics;
-using Avalonia.Metadata;
-
-namespace Avalonia.Data
-{
- ///
- /// Holds a binding that can be applied to a property on an object.
- ///
- [NotClientImplementable]
- public interface IBinding
- {
- ///
- /// Initiates the binding on a target object.
- ///
- /// The target instance.
- /// The target property. May be null.
- ///
- /// An optional anchor from which to locate required context. When binding to objects that
- /// are not in the logical tree, certain types of binding need an anchor into the tree in
- /// order to locate named controls or resources. The parameter
- /// can be used to provide this context.
- ///
- /// Whether data validation should be enabled.
- ///
- /// A or null if the binding could not be resolved.
- ///
- [Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
- InstancedBinding? Initiate(
- AvaloniaObject target,
- AvaloniaProperty? targetProperty,
- object? anchor = null,
- bool enableDataValidation = false);
- }
-}
diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs
index d12beb114b..c1cfb5a347 100644
--- a/src/Avalonia.Base/Data/IndexerBinding.cs
+++ b/src/Avalonia.Base/Data/IndexerBinding.cs
@@ -4,7 +4,7 @@ using Avalonia.Diagnostics;
namespace Avalonia.Data
{
- internal class IndexerBinding : IBinding2
+ internal class IndexerBinding : BindingBase
{
public IndexerBinding(
AvaloniaObject source,
@@ -16,22 +16,15 @@ namespace Avalonia.Data
Mode = mode;
}
- private AvaloniaObject Source { get; }
public AvaloniaProperty Property { get; }
+
+ private AvaloniaObject Source { get; }
private BindingMode Mode { get; }
- [Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
- public InstancedBinding? Initiate(
+ internal override BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
- object? anchor = null,
- bool enableDataValidation = false)
- {
- var expression = new IndexerBindingExpression(Source, Property, target, targetProperty, Mode);
- return new InstancedBinding(expression, Mode, BindingPriority.LocalValue);
- }
-
- BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
+ object? anchor)
{
return new IndexerBindingExpression(Source, Property, target, targetProperty, Mode);
}
diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs
deleted file mode 100644
index f174be7f40..0000000000
--- a/src/Avalonia.Base/Data/InstancedBinding.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using System;
-using System.ComponentModel;
-using Avalonia.Data.Core;
-using Avalonia.Reactive;
-using ObservableEx = Avalonia.Reactive.Observable;
-
-namespace Avalonia.Data
-{
- ///
- /// Holds the result of calling .
- ///
- ///
- /// Whereas an holds a description of a binding such as "Bind to the X
- /// property on a control's DataContext"; this class represents a binding that has been
- /// *instanced* by calling
- /// on a target object.
- ///
- public sealed class InstancedBinding
- {
- private readonly AvaloniaObject? _target;
- private readonly UntypedBindingExpressionBase? _expression;
- private IObservable? _observable;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The binding source.
- /// The binding mode.
- /// The priority of the binding.
- ///
- /// This constructor can be used to create any type of binding and as such requires an
- /// as the binding source because this is the only binding
- /// source which can be used for all binding modes. If you wish to create an instance with
- /// something other than a subject, use one of the static creation methods on this class.
- ///
- internal InstancedBinding(IObservable source, BindingMode mode, BindingPriority priority)
- {
- Mode = mode;
- Priority = priority;
- _observable = source ?? throw new ArgumentNullException(nameof(source));
- }
-
- internal InstancedBinding(
- UntypedBindingExpressionBase source,
- BindingMode mode,
- BindingPriority priority)
- {
- Mode = mode;
- Priority = priority;
- _expression = source ?? throw new ArgumentNullException(nameof(source));
- }
-
- internal InstancedBinding(
- AvaloniaObject? target,
- UntypedBindingExpressionBase source,
- BindingMode mode,
- BindingPriority priority)
- {
- Mode = mode;
- Priority = priority;
- _expression = source ?? throw new ArgumentNullException(nameof(source));
- _target = target;
- }
-
- ///
- /// Gets the binding mode with which the binding was initiated.
- ///
- public BindingMode Mode { get; }
-
- ///
- /// Gets the binding priority.
- ///
- public BindingPriority Priority { get; }
-
- ///
- /// Gets the binding source observable.
- ///
- public IObservable Source => _observable ??= _expression!.ToObservable(_target);
-
- [Obsolete("Use Source property"), EditorBrowsable(EditorBrowsableState.Never)]
- public IObservable Observable => Source;
-
- internal UntypedBindingExpressionBase? Expression => _expression;
-
- ///
- /// Creates a new one-time binding with a fixed value.
- ///
- /// The value.
- /// The priority of the binding.
- /// An instance.
- public static InstancedBinding OneTime(
- object value,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- return new InstancedBinding(ObservableEx.SingleValue(value), BindingMode.OneTime, priority);
- }
-
- ///
- /// Creates a new one-time binding.
- ///
- /// The source observable.
- /// The priority of the binding.
- /// An instance.
- public static InstancedBinding OneTime(
- IObservable observable,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- _ = observable ?? throw new ArgumentNullException(nameof(observable));
-
- return new InstancedBinding(observable, BindingMode.OneTime, priority);
- }
-
- ///
- /// Creates a new one-way binding.
- ///
- /// The source observable.
- /// The priority of the binding.
- /// An instance.
- public static InstancedBinding OneWay(
- IObservable observable,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- _ = observable ?? throw new ArgumentNullException(nameof(observable));
-
- return new InstancedBinding(observable, BindingMode.OneWay, priority);
- }
-
- ///
- /// Creates a new one-way to source binding.
- ///
- /// The binding source.
- /// The priority of the binding.
- /// An instance.
- public static InstancedBinding OneWayToSource(
- IObserver observer,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- _ = observer ?? throw new ArgumentNullException(nameof(observer));
-
- return new InstancedBinding((IObservable)observer, BindingMode.OneWayToSource, priority);
- }
-
- ///
- /// Creates a new two-way binding.
- ///
- /// The binding source.
- /// The binding source.
- /// The priority of the binding.
- /// An instance.
- public static InstancedBinding TwoWay(
- IObservable observable,
- IObserver observer,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- _ = observable ?? throw new ArgumentNullException(nameof(observable));
- _ = observer ?? throw new ArgumentNullException(nameof(observer));
-
- var subject = observable == observer ? observable : new CombinedSubject(observer, observable);
- return new InstancedBinding(subject, BindingMode.TwoWay, priority);
- }
-
- ///
- /// Creates a copy of the with a different priority.
- ///
- /// The priority of the binding.
- /// An instance.
- public InstancedBinding WithPriority(BindingPriority priority)
- {
- return new InstancedBinding(Source, Mode, priority);
- }
- }
-}
diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Avalonia.Base/Data/MultiBinding.cs
similarity index 85%
rename from src/Markup/Avalonia.Markup/Data/MultiBinding.cs
rename to src/Avalonia.Base/Data/MultiBinding.cs
index f894300a07..37ea8ec368 100644
--- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
+++ b/src/Avalonia.Base/Data/MultiBinding.cs
@@ -1,25 +1,23 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
-using System.Linq;
-using Avalonia.Reactive;
using Avalonia.Data.Converters;
-using Avalonia.Metadata;
using Avalonia.Data.Core;
-using System.ComponentModel;
+using Avalonia.Metadata;
namespace Avalonia.Data
{
///
/// A XAML binding that calculates an aggregate value from multiple child .
///
- public class MultiBinding : IBinding2
+ public sealed class MultiBinding : BindingBase
{
///
/// Gets the collection of child bindings.
///
[Content, AssignBinding]
- public IList Bindings { get; set; } = new List();
+ public IList Bindings { get; set; } = new List();
///
/// Gets or sets the to use.
@@ -77,18 +75,7 @@ namespace Avalonia.Data
TargetNullValue = AvaloniaProperty.UnsetValue;
}
- ///
- public InstancedBinding? Initiate(
- AvaloniaObject target,
- AvaloniaProperty? targetProperty,
- object? anchor = null,
- bool enableDataValidation = false)
- {
- var expression = InstanceCore(target, targetProperty);
- return new InstancedBinding(target, expression, Mode, Priority);
- }
-
- BindingExpressionBase IBinding2.Instance(
+ internal override BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor)
diff --git a/src/Avalonia.Base/Data/ReflectionBinding.cs b/src/Avalonia.Base/Data/ReflectionBinding.cs
new file mode 100644
index 0000000000..299f6317b4
--- /dev/null
+++ b/src/Avalonia.Base/Data/ReflectionBinding.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.ExpressionNodes;
+using Avalonia.Data.Core.Parsers;
+using Avalonia.Metadata;
+using Avalonia.Utilities;
+
+namespace Avalonia.Data
+{
+ ///
+ /// A binding that uses reflection to access members.
+ ///
+ [RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
+ [RequiresDynamicCode(TrimmingMessages.ReflectionBindingRequiresDynamicCodeMessage)]
+ public class ReflectionBinding : BindingBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ReflectionBinding()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The binding path.
+ public ReflectionBinding(string 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 name of the element to use as the binding source.
+ ///
+ public string? ElementName { 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.
+ ///
+ [ConstructorArgument("path")]
+ public string Path { get; set; } = "";
+
+ ///
+ /// Gets or sets the binding priority.
+ ///
+ public BindingPriority Priority { get; set; }
+
+ ///
+ /// Gets or sets the relative source for the binding.
+ ///
+ public RelativeSource? RelativeSource { 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; }
+
+ ///
+ /// Gets or sets a function used to resolve types from names in the binding path.
+ ///
+ public Func? TypeResolver { get; set; }
+
+ internal WeakReference? DefaultAnchor { get; set; }
+ internal WeakReference? NameScope { get; set; }
+
+ internal override BindingExpressionBase CreateInstance(
+ AvaloniaObject target,
+ AvaloniaProperty? targetProperty,
+ object? anchor)
+ {
+ List? nodes = null;
+ var isRooted = false;
+ var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false;
+
+ // Build the expression nodes from the binding path.
+ if (!string.IsNullOrEmpty(Path))
+ {
+ var reader = new CharacterReader(Path.AsSpan());
+ var (astPool, sourceMode) = BindingExpressionGrammar.ParseToPooledList(ref reader);
+ nodes = ExpressionNodeFactory.CreateFromAst(
+ astPool,
+ TypeResolver,
+ GetNameScope(),
+ 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 source node. The type of source node will
+ // depend on the ElementName and RelativeSource properties of the binding and if
+ // neither of those are set will default to a data context node.
+ if (Source == AvaloniaProperty.UnsetValue && !isRooted && CreateSourceNode(targetProperty) is { } sourceNode)
+ {
+ nodes ??= new();
+ nodes.Insert(0, sourceNode);
+ }
+
+ // 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,
+ targetProperty: targetProperty,
+ targetNullValue: TargetNullValue,
+ targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
+ updateSourceTrigger: trigger);
+ }
+
+ private INameScope? GetNameScope()
+ {
+ INameScope? result = null;
+ NameScope?.TryGetTarget(out result);
+ return result;
+ }
+
+ private ExpressionNode? CreateSourceNode(AvaloniaProperty? targetProperty)
+ {
+ if (!string.IsNullOrEmpty(ElementName))
+ {
+ var nameScope = GetNameScope() ?? throw new InvalidOperationException(
+ "Cannot create ElementName binding when NameScope is null");
+ return new NamedElementNode(nameScope, ElementName);
+ }
+
+ if (RelativeSource is not null)
+ return ExpressionNodeFactory.CreateRelativeSource(RelativeSource);
+
+ return ExpressionNodeFactory.CreateDataContext(targetProperty);
+ }
+
+ 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/Data/RelativeSource.cs b/src/Avalonia.Base/Data/RelativeSource.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup/Data/RelativeSource.cs
rename to src/Avalonia.Base/Data/RelativeSource.cs
diff --git a/src/Avalonia.Base/Data/TemplateBinding.Observable.cs b/src/Avalonia.Base/Data/TemplateBinding.Observable.cs
deleted file mode 100644
index 21bf4430b6..0000000000
--- a/src/Avalonia.Base/Data/TemplateBinding.Observable.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using Avalonia.Reactive;
-
-namespace Avalonia.Data
-{
- // TODO12: Remove IAvaloniaSubject support from TemplateBinding.
- public partial class TemplateBinding : IAvaloniaSubject
- {
- private IAvaloniaSubject? _observableAdapter;
-
- public IDisposable Subscribe(IObserver observer)
- {
- _observableAdapter ??= ToObservable();
- return _observableAdapter.Subscribe(observer);
- }
-
- void IObserver.OnCompleted() => _observableAdapter?.OnCompleted();
- void IObserver.OnError(Exception error) => _observableAdapter?.OnError(error);
- void IObserver.OnNext(object? value) => _observableAdapter?.OnNext(value);
- }
-}
diff --git a/src/Avalonia.Base/Data/TemplateBinding.cs b/src/Avalonia.Base/Data/TemplateBinding.cs
index 2b0e054c07..55b3e74ce5 100644
--- a/src/Avalonia.Base/Data/TemplateBinding.cs
+++ b/src/Avalonia.Base/Data/TemplateBinding.cs
@@ -1,35 +1,21 @@
using System;
using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Data.Converters;
-using Avalonia.Data.Core;
-using Avalonia.Logging;
using Avalonia.Metadata;
-using Avalonia.Styling;
namespace Avalonia.Data
{
///
/// A XAML binding to a property on a control's templated parent.
///
- public partial class TemplateBinding : UntypedBindingExpressionBase,
- IBinding,
- IBinding2,
- IDescription,
- ISetterValue,
- IDisposable
+ public sealed partial class TemplateBinding : BindingBase
{
- private bool _isSetterValue;
- private bool _hasPublishedValue;
-
public TemplateBinding()
- : base(BindingPriority.Template)
{
}
public TemplateBinding([InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)] AvaloniaProperty property)
- : base(BindingPriority.Template)
{
Property = property;
}
@@ -57,202 +43,28 @@ namespace Avalonia.Data
///
/// Gets or sets the binding mode.
///
- public new BindingMode Mode
- {
- get => base.Mode;
- set => base.Mode = value;
- }
+ public BindingMode Mode { get; set; }
///
/// Gets or sets the name of the source property on the templated parent.
///
+ [ConstructorArgument("property")]
[InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)]
public AvaloniaProperty? Property { get; set; }
- ///
- public override string Description => "TemplateBinding: " + Property;
-
- public IBinding ProvideValue() => this;
-
- public InstancedBinding? Initiate(
- AvaloniaObject target,
- AvaloniaProperty? targetProperty,
- object? anchor = null,
- bool enableDataValidation = false)
- {
- return new(target, InstanceCore(), Mode, BindingPriority.Template);
- }
-
- BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
- {
- return InstanceCore();
- }
-
- internal override bool WriteValueToSource(object? value)
- {
- if (Property is not null && TryGetTemplatedParent(out var templatedParent))
- {
- if (Converter is not null)
- value = ConvertBack(Converter, ConverterCulture, ConverterParameter, value, TargetType);
-
- if (value != BindingOperations.DoNothing)
- templatedParent.SetCurrentValue(Property, value);
-
- return true;
- }
+ public BindingBase ProvideValue() => this;
- return false;
- }
-
- ///
- void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true;
-
- protected override void StartCore()
- {
- _hasPublishedValue = false;
- OnTemplatedParentChanged();
- if (TryGetTarget(out var target))
- target.PropertyChanged += OnTargetPropertyChanged;
- }
-
- protected override void StopCore()
- {
- if (TryGetTarget(out var target))
- {
- if (target is StyledElement targetElement &&
- targetElement?.TemplatedParent is { } templatedParent)
- {
- templatedParent.PropertyChanged -= OnTemplatedParentPropertyChanged;
- }
-
- if (target is not null)
- {
- target.PropertyChanged -= OnTargetPropertyChanged;
- }
- }
- }
-
- private object? ConvertToTargetType(object? value)
- {
- var converter = TargetTypeConverter.GetDefaultConverter();
-
- if (converter.TryConvert(value, TargetType, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- else
- {
- if (TryGetTarget(out var target))
- {
- var valueString = value?.ToString() ?? "(null)";
- var valueTypeName = value?.GetType().FullName ?? "null";
- var message = $"Could not convert '{valueString}' ({valueTypeName}) to '{TargetType}'.";
- Log(target, message, LogEventLevel.Warning);
- }
-
- return AvaloniaProperty.UnsetValue;
- }
- }
-
- private TemplateBinding InstanceCore()
+ internal override BindingExpressionBase CreateInstance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
{
if (Mode is BindingMode.OneTime or BindingMode.OneWayToSource)
throw new NotSupportedException("TemplateBinding does not support OneTime or OneWayToSource bindings.");
- // Usually each `TemplateBinding` will only be instantiated once; in this case we can
- // use the `TemplateBinding` object itself as the binding expression in order to save
- // allocating a new object.
- //
- // If the binding appears in a `Setter`, then make a clone and instantiate that because
- // because the setter can outlive the control and cause a leak.
- if (!_isSetterValue)
- {
- return this;
- }
- else
- {
- var clone = new TemplateBinding
- {
- Converter = Converter,
- ConverterCulture = ConverterCulture,
- ConverterParameter = ConverterParameter,
- Mode = Mode,
- Property = Property,
- };
-
- return clone;
- }
- }
-
- private void PublishValue()
- {
- if (Mode == BindingMode.OneWayToSource)
- return;
-
- if (TryGetTemplatedParent(out var templatedParent))
- {
- var value = Property is not null ?
- templatedParent.GetValue(Property) :
- templatedParent;
- BindingError? error = null;
-
- if (Converter is not null)
- value = Convert(Converter, ConverterCulture, ConverterParameter, value, TargetType, ref error);
-
- value = ConvertToTargetType(value);
- PublishValue(value, error);
- _hasPublishedValue = true;
-
- if (Mode == BindingMode.OneTime)
- Stop();
- }
- else if (_hasPublishedValue)
- {
- PublishValue(AvaloniaProperty.UnsetValue);
- }
- }
-
- private void OnTemplatedParentChanged()
- {
- if (TryGetTemplatedParent(out var templatedParent))
- templatedParent.PropertyChanged += OnTemplatedParentPropertyChanged;
-
- PublishValue();
- }
-
- private void OnTemplatedParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Property == Property)
- PublishValue();
- }
-
- private void OnTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Property == StyledElement.TemplatedParentProperty)
- {
- if (e.OldValue is AvaloniaObject oldValue)
- oldValue.PropertyChanged -= OnTemplatedParentPropertyChanged;
-
- OnTemplatedParentChanged();
- }
- else if (Mode is BindingMode.TwoWay or BindingMode.OneWayToSource && e.Property == TargetProperty)
- {
- WriteValueToSource(e.NewValue);
- }
- }
-
- private bool TryGetTemplatedParent([NotNullWhen(true)] out AvaloniaObject? result)
- {
- if (TryGetTarget(out var target) &&
- target is StyledElement targetElement &&
- targetElement.TemplatedParent is { } templatedParent)
- {
- result = templatedParent;
- return true;
- }
-
- result = null;
- return false;
+ return new TemplateBindingExpression(
+ Property,
+ Converter,
+ ConverterCulture,
+ ConverterParameter,
+ Mode);
}
}
}
diff --git a/src/Avalonia.Base/Data/TemplateBindingExpression.cs b/src/Avalonia.Base/Data/TemplateBindingExpression.cs
new file mode 100644
index 0000000000..de6b373eec
--- /dev/null
+++ b/src/Avalonia.Base/Data/TemplateBindingExpression.cs
@@ -0,0 +1,170 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Avalonia.Data.Converters;
+using Avalonia.Data.Core;
+using Avalonia.Logging;
+
+namespace Avalonia.Data;
+
+internal class TemplateBindingExpression : UntypedBindingExpressionBase
+{
+ private IValueConverter? _converter;
+ private CultureInfo? _converterCulture;
+ private object? _converterParameter;
+ private BindingMode _mode;
+ private readonly AvaloniaProperty? _property;
+ private bool _hasPublishedValue;
+
+ public TemplateBindingExpression(
+ AvaloniaProperty? property,
+ IValueConverter? converter,
+ CultureInfo? converterCulture,
+ object? converterParameter,
+ BindingMode mode)
+ : base(BindingPriority.Template)
+ {
+ _property = property;
+ _converter = converter;
+ _converterCulture = converterCulture;
+ _converterParameter = converterParameter;
+ _mode = mode;
+ }
+
+ public override string Description => $"{{TemplateBinding {_property}}}";
+
+ protected override void StartCore()
+ {
+ _hasPublishedValue = false;
+ OnTemplatedParentChanged();
+ if (TryGetTarget(out var target))
+ target.PropertyChanged += OnTargetPropertyChanged;
+ }
+
+ protected override void StopCore()
+ {
+ if (TryGetTarget(out var target))
+ {
+ if (target is StyledElement targetElement &&
+ targetElement?.TemplatedParent is { } templatedParent)
+ {
+ templatedParent.PropertyChanged -= OnTemplatedParentPropertyChanged;
+ }
+
+ if (target is not null)
+ {
+ target.PropertyChanged -= OnTargetPropertyChanged;
+ }
+ }
+ }
+
+ internal override bool WriteValueToSource(object? value)
+ {
+ if (_property is not null && TryGetTemplatedParent(out var templatedParent))
+ {
+ if (_converter is not null)
+ value = ConvertBack(_converter, _converterCulture, _converterParameter, value, TargetType);
+
+ if (value != BindingOperations.DoNothing)
+ templatedParent.SetCurrentValue(_property, value);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private object? ConvertToTargetType(object? value)
+ {
+ var converter = TargetTypeConverter.GetDefaultConverter();
+
+ if (converter.TryConvert(value, TargetType, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ else
+ {
+ if (TryGetTarget(out var target))
+ {
+ var valueString = value?.ToString() ?? "(null)";
+ var valueTypeName = value?.GetType().FullName ?? "null";
+ var message = $"Could not convert '{valueString}' ({valueTypeName}) to '{TargetType}'.";
+ Log(target, message, LogEventLevel.Warning);
+ }
+
+ return AvaloniaProperty.UnsetValue;
+ }
+ }
+
+ private void PublishValue()
+ {
+ if (_mode == BindingMode.OneWayToSource)
+ return;
+
+ if (TryGetTemplatedParent(out var templatedParent))
+ {
+ var value = _property is not null ?
+ templatedParent.GetValue(_property) :
+ templatedParent;
+ BindingError? error = null;
+
+ if (_converter is not null)
+ value = Convert(_converter, _converterCulture, _converterParameter, value, TargetType, ref error);
+
+ value = ConvertToTargetType(value);
+ PublishValue(value, error);
+ _hasPublishedValue = true;
+
+ if (_mode == BindingMode.OneTime)
+ Stop();
+ }
+ else if (_hasPublishedValue)
+ {
+ PublishValue(AvaloniaProperty.UnsetValue);
+ }
+ }
+
+ private void OnTemplatedParentChanged()
+ {
+ if (TryGetTemplatedParent(out var templatedParent))
+ templatedParent.PropertyChanged += OnTemplatedParentPropertyChanged;
+
+ PublishValue();
+ }
+
+ private void OnTemplatedParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == _property)
+ PublishValue();
+ }
+
+ private void OnTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == StyledElement.TemplatedParentProperty)
+ {
+ if (e.OldValue is AvaloniaObject oldValue)
+ oldValue.PropertyChanged -= OnTemplatedParentPropertyChanged;
+
+ OnTemplatedParentChanged();
+ }
+ else if (_mode is BindingMode.TwoWay or BindingMode.OneWayToSource && e.Property == TargetProperty)
+ {
+ WriteValueToSource(e.NewValue);
+ }
+ }
+
+ private bool TryGetTemplatedParent([NotNullWhen(true)] out AvaloniaObject? result)
+ {
+ if (TryGetTarget(out var target) &&
+ target is StyledElement targetElement &&
+ targetElement.TemplatedParent is { } templatedParent)
+ {
+ result = templatedParent;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+}
diff --git a/src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs b/src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs
deleted file mode 100644
index bfada91abe..0000000000
--- a/src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Avalonia.Diagnostics;
-
-internal static class ObsoletionMessages
-{
- public const string MayBeRemovedInAvalonia12 = "This API may be removed in Avalonia 12. If you depend on this API, please open an issue with details of your use-case.";
-}
diff --git a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs b/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
deleted file mode 100644
index 3db48cd69c..0000000000
--- a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Metadata;
-using Avalonia.Styling;
-
-namespace Avalonia.Diagnostics;
-
-[PrivateApi]
-[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")]
-public class StyleDiagnostics
-{
- ///
- /// Currently applied styles.
- ///
- public IReadOnlyList AppliedStyles { get; }
-
- public StyleDiagnostics(IReadOnlyList appliedStyles)
- {
- AppliedStyles = appliedStyles;
- }
-}
-
-[PrivateApi]
-[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")]
-public sealed class AppliedStyle
-{
- private readonly StyleInstance _instance;
-
- internal AppliedStyle(StyleInstance instance)
- {
- _instance = instance;
- }
-
- public bool HasActivator => _instance.HasActivator;
- public bool IsActive => _instance.IsActive();
- public StyleBase Style => (StyleBase)_instance.Source;
-}
diff --git a/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs b/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs
index 2b0d6c3c64..27683dbe72 100644
--- a/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs
+++ b/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs
@@ -39,7 +39,4 @@ internal class StyleValueFrameDiagnostic : IValueFrameDiagnostic
}
}
}
-
- [Unstable("Compatibility with 11.x")]
- public AppliedStyle AsAppliedStyle() => new AppliedStyle(_styleInstance);
}
diff --git a/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs b/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs
deleted file mode 100644
index 78ab52a6bc..0000000000
--- a/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Linq;
-using Avalonia.Metadata;
-using Avalonia.Styling;
-
-namespace Avalonia.Diagnostics;
-
-///
-/// Defines diagnostic extensions on s.
-///
-[PrivateApi]
-public static class StyledElementExtensions
-{
- [Obsolete("Use AvaloniaObjectExtensions.GetValueStoreDiagnostic instead", true)]
- public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement)
- {
- var diagnostics = styledElement.GetValueStore().GetStoreDiagnostic();
- return new StyleDiagnostics(diagnostics.AppliedFrames
- .OfType()
- .Select(f => f.AsAppliedStyle())
- .ToArray());
- }
-}
-
diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/IOptionalFeatureProvider.cs
similarity index 92%
rename from src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
rename to src/Avalonia.Base/IOptionalFeatureProvider.cs
index 27c2243791..55c3ef7984 100644
--- a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
+++ b/src/Avalonia.Base/IOptionalFeatureProvider.cs
@@ -1,8 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
-// TODO12: move to Avalonia namespace.
-namespace Avalonia.Platform;
+namespace Avalonia;
public interface IOptionalFeatureProvider
{
diff --git a/src/Avalonia.Base/Input/DataFormats.cs b/src/Avalonia.Base/Input/DataFormats.cs
index 2fe818f20d..935f03de25 100644
--- a/src/Avalonia.Base/Input/DataFormats.cs
+++ b/src/Avalonia.Base/Input/DataFormats.cs
@@ -1,56 +1,11 @@
using System;
-using System.ComponentModel;
-using Avalonia.Input.Platform;
-namespace Avalonia.Input
-{
- public static class DataFormats
- {
- ///
- /// Dataformat for plaintext
- ///
- [Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.Text)} instead.")]
- public static readonly string Text = nameof(Text);
+namespace Avalonia.Input;
- ///
- /// Dataformat for one or more files.
- ///
- [Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead.")]
- public static readonly string Files = nameof(Files);
-
- ///
- /// Dataformat for one or more filenames
- ///
- ///
- /// This data format is supported only on desktop platforms.
- ///
- [Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead."), EditorBrowsable(EditorBrowsableState.Never)]
- public static readonly string FileNames = nameof(FileNames);
-
-#pragma warning disable CS0618 // Type or member is obsolete
-
- internal static DataFormat ToDataFormat(string format)
- {
- if (format == Text)
- return DataFormat.Text;
-
- if (format == Files || format == FileNames)
- return DataFormat.File;
-
- return DataFormat.CreateBytesPlatformFormat(format);
- }
-
- internal static string ToString(DataFormat format)
- {
- if (DataFormat.Text.Equals(format))
- return Text;
-
- if (DataFormat.File.Equals(format))
- return Files;
-
- return format.Identifier;
- }
-
-#pragma warning restore CS0618 // Type or member is obsolete
- }
-}
+// TODO13: remove
+///
+/// This class does not do anything anymore.
+/// Use instead.
+///
+[Obsolete($"Use {nameof(DataFormat)} instead", true)]
+public static class DataFormats;
diff --git a/src/Avalonia.Base/Input/DataObject.cs b/src/Avalonia.Base/Input/DataObject.cs
index dc9e2a29af..708ac61e16 100644
--- a/src/Avalonia.Base/Input/DataObject.cs
+++ b/src/Avalonia.Base/Input/DataObject.cs
@@ -1,40 +1,11 @@
using System;
-using System.Collections.Generic;
-namespace Avalonia.Input
-{
- ///
- /// Specific and mutable implementation of the IDataObject interface.
- ///
- [Obsolete($"Use {nameof(DataTransfer)} instead")]
- public class DataObject : IDataObject
- {
- private readonly Dictionary _items = new();
+namespace Avalonia.Input;
- ///
- public bool Contains(string dataFormat)
- {
- return _items.ContainsKey(dataFormat);
- }
-
- ///
- public object? Get(string dataFormat)
- {
- return _items.TryGetValue(dataFormat, out var item) ? item : null;
- }
-
- ///
- public IEnumerable GetDataFormats()
- {
- return _items.Keys;
- }
-
- ///
- /// Sets a value to the internal store of the data object with as a key.
- ///
- public void Set(string dataFormat, object value)
- {
- _items[dataFormat] = value;
- }
- }
-}
+// TODO13: remove
+///
+/// This class does not do anything anymore.
+/// Use instead.
+///
+[Obsolete($"Use {nameof(DataTransfer)} instead", true)]
+public sealed class DataObject;
diff --git a/src/Avalonia.Base/Input/DataObjectExtensions.cs b/src/Avalonia.Base/Input/DataObjectExtensions.cs
deleted file mode 100644
index f361485404..0000000000
--- a/src/Avalonia.Base/Input/DataObjectExtensions.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using Avalonia.Platform.Storage;
-
-#pragma warning disable CS0618 // Type or member is obsolete
-
-namespace Avalonia.Input
-{
- // TODO12: remove
- public static class DataObjectExtensions
- {
- ///
- /// Returns a list of files if the DataObject contains files or filenames.
- /// .
- ///
- ///
- /// Collection of storage items - files or folders. If format isn't available, returns null.
- ///
- public static IEnumerable? GetFiles(this IDataObject dataObject)
- {
- return dataObject.Get(DataFormats.Files) as IEnumerable;
- }
-
- ///
- /// Returns a list of filenames if the DataObject contains filenames.
- ///
- ///
- ///
- /// Collection of file names. If format isn't available, returns null.
- ///
- [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)]
- public static IEnumerable? GetFileNames(this IDataObject dataObject)
- {
- return (dataObject.Get(DataFormats.FileNames) as IEnumerable)
- ?? dataObject.GetFiles()?
- .Select(f => f.TryGetLocalPath())
- .Where(p => !string.IsNullOrEmpty(p))
- .OfType();
- }
-
- ///
- /// Returns the dragged text if the DataObject contains any text.
- ///
- ///
- ///
- /// A text string. If format isn't available, returns null.
- ///
- public static string? GetText(this IDataObject dataObject)
- {
- return dataObject.Get(DataFormats.Text) as string;
- }
- }
-}
diff --git a/src/Avalonia.Base/Input/DataTransferExtensions.cs b/src/Avalonia.Base/Input/DataTransferExtensions.cs
index 517ca51719..a4f74bef3e 100644
--- a/src/Avalonia.Base/Input/DataTransferExtensions.cs
+++ b/src/Avalonia.Base/Input/DataTransferExtensions.cs
@@ -1,7 +1,5 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
-using Avalonia.Input.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
@@ -14,11 +12,6 @@ namespace Avalonia.Input;
///
public static class DataTransferExtensions
{
- [Obsolete]
- internal static IDataObject ToLegacyDataObject(this IDataTransfer dataTransfer)
- => (dataTransfer as DataObjectToDataTransferWrapper)?.DataObject
- ?? new DataTransferToDataObjectWrapper(dataTransfer);
-
///
/// Gets whether a supports a specific format.
///
diff --git a/src/Avalonia.Base/Input/DragDrop.cs b/src/Avalonia.Base/Input/DragDrop.cs
index 551f436854..33f538c443 100644
--- a/src/Avalonia.Base/Input/DragDrop.cs
+++ b/src/Avalonia.Base/Input/DragDrop.cs
@@ -122,16 +122,6 @@ namespace Avalonia.Input
element.RemoveHandler(DropEvent, handler);
}
- ///
- /// Starts a dragging operation with the given and returns the applied drop effect from the target.
- ///
- ///
- [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")]
- public static Task DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
- {
- return DoDragDropAsync(triggerEvent, new DataObjectToDataTransferWrapper(data), allowedEffects);
- }
-
///
/// Starts a dragging operation with the given and returns the applied drop effect from the target.
///
diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs
index cf86f22c2b..e68a6138e0 100644
--- a/src/Avalonia.Base/Input/DragEventArgs.cs
+++ b/src/Avalonia.Base/Input/DragEventArgs.cs
@@ -1,24 +1,18 @@
using System;
-using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Metadata;
namespace Avalonia.Input
{
- public class DragEventArgs : RoutedEventArgs
+ public class DragEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
private readonly Interactive _target;
private readonly Point _targetLocation;
- [Obsolete] private IDataObject? _legacyDataObject;
public DragDropEffects DragEffects { get; set; }
public IDataTransfer DataTransfer { get; }
- [Obsolete($"Use {nameof(DataTransfer)} instead.")]
- public IDataObject Data
- => _legacyDataObject ??= DataTransfer.ToLegacyDataObject();
-
public KeyModifiers KeyModifiers { get; }
public Point GetPosition(Visual relativeTo)
@@ -31,17 +25,6 @@ namespace Avalonia.Input
return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
}
- [Obsolete($"Use the constructor accepting a {nameof(IDataTransfer)} instance instead.")]
- public DragEventArgs(
- RoutedEvent routedEvent,
- IDataObject data,
- Interactive target,
- Point targetLocation,
- KeyModifiers keyModifiers)
- : this(routedEvent, new DataObjectToDataTransferWrapper(data), target, targetLocation, keyModifiers)
- {
- }
-
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")]
public DragEventArgs(
RoutedEvent routedEvent,
diff --git a/src/Avalonia.Base/Input/FocusChangingEventArgs.cs b/src/Avalonia.Base/Input/FocusChangingEventArgs.cs
index 0c392c1989..372ddf38b6 100644
--- a/src/Avalonia.Base/Input/FocusChangingEventArgs.cs
+++ b/src/Avalonia.Base/Input/FocusChangingEventArgs.cs
@@ -7,7 +7,7 @@ using Avalonia.Interactivity;
namespace Avalonia.Input
{
- public class FocusChangingEventArgs : RoutedEventArgs
+ public class FocusChangingEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
///
/// Provides data for focus changing.
diff --git a/src/Avalonia.Base/Input/GotFocusEventArgs.cs b/src/Avalonia.Base/Input/GotFocusEventArgs.cs
index 8d15c3f9ec..658bf5aae5 100644
--- a/src/Avalonia.Base/Input/GotFocusEventArgs.cs
+++ b/src/Avalonia.Base/Input/GotFocusEventArgs.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Input
///
/// Holds arguments for a .
///
- public class GotFocusEventArgs : RoutedEventArgs
+ public class GotFocusEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
public GotFocusEventArgs() : base(InputElement.GotFocusEvent)
{
diff --git a/src/Avalonia.Base/Input/IDataObject.cs b/src/Avalonia.Base/Input/IDataObject.cs
deleted file mode 100644
index 560ffc0c02..0000000000
--- a/src/Avalonia.Base/Input/IDataObject.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Avalonia.Input
-{
- ///
- /// Interface to access information about the data of a drag-and-drop operation.
- ///
- [Obsolete($"Use {nameof(IDataTransfer)} or {nameof(IAsyncDataTransfer)} instead")]
- public interface IDataObject
- {
- ///
- /// Lists all formats which are present in the DataObject.
- ///
- ///
- IEnumerable GetDataFormats();
-
- ///
- /// Checks whether a given DataFormat is present in this object
- ///
- ///
- bool Contains(string dataFormat);
-
- ///
- /// Tries to get the data of the given DataFormat.
- ///
- ///
- /// Object data. If format isn't available, returns null.
- ///
- object? Get(string dataFormat);
- }
-}
diff --git a/src/Avalonia.Base/Input/IKeyModifiersEventArgs.cs b/src/Avalonia.Base/Input/IKeyModifiersEventArgs.cs
new file mode 100644
index 0000000000..69770b47a3
--- /dev/null
+++ b/src/Avalonia.Base/Input/IKeyModifiersEventArgs.cs
@@ -0,0 +1,15 @@
+using Avalonia.Metadata;
+
+namespace Avalonia.Input;
+
+///
+/// Represents an event associated with a set of .
+///
+[NotClientImplementable]
+public interface IKeyModifiersEventArgs
+{
+ ///
+ /// Gets the key modifiers associated with this event.
+ ///
+ KeyModifiers KeyModifiers { 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/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs
index 864aa02617..0fed020a91 100644
--- a/src/Avalonia.Base/Input/KeyEventArgs.cs
+++ b/src/Avalonia.Base/Input/KeyEventArgs.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Input;
///
/// Provides information specific to a keyboard event.
///
-public class KeyEventArgs : RoutedEventArgs
+public class KeyEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
///
///
@@ -33,9 +33,6 @@ public class KeyEventArgs : RoutedEventArgs
///
public Key Key { get; init; }
- ///
- /// Gets the key modifiers for the associated event.
- ///
public KeyModifiers KeyModifiers { get; init; }
///
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/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs
index 5a0cdad755..62105c7deb 100644
--- a/src/Avalonia.Base/Input/MouseDevice.cs
+++ b/src/Avalonia.Base/Input/MouseDevice.cs
@@ -34,6 +34,8 @@ namespace Avalonia.Input
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
}
+ internal Pointer Pointer => _pointer;
+
internal static TMouseDevice GetOrCreatePrimary() where TMouseDevice : MouseDevice, new()
{
if (_primary is TMouseDevice device)
diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
index 5cc0d1a564..0f529142ca 100644
--- a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
+++ b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
@@ -65,7 +65,7 @@ public partial class XYFocus
private static bool IsValidCandidate(InputElement candidate, KeyDeviceType? inputKeyDeviceType)
{
- return candidate.Focusable && candidate.IsEnabled && candidate.IsVisible
+ return candidate.Focusable && candidate.IsEffectivelyEnabled && candidate.IsEffectivelyVisible
// Only allow candidate focus, if original key device type could focus it.
&& XYFocusHelpers.IsAllowedXYNavigationMode(candidate, inputKeyDeviceType);
}
@@ -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/Platform/BinaryFormatterHelper.cs b/src/Avalonia.Base/Input/Platform/BinaryFormatterHelper.cs
deleted file mode 100644
index 6e38a8afac..0000000000
--- a/src/Avalonia.Base/Input/Platform/BinaryFormatterHelper.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Runtime.Serialization.Formatters.Binary;
-using Avalonia.Compatibility;
-using Avalonia.Logging;
-
-namespace Avalonia.Input.Platform;
-
-// TODO12: remove
-[Obsolete("Remove in v12")]
-internal static class BinaryFormatterHelper
-{
- // Compatibility with WinForms + WPF...
- private static ReadOnlySpan SerializedObjectGuid
- => [
- // FD9EA796-3B13-4370-A679-56106BB288FB
- 0x96, 0xa7, 0x9e, 0xfd,
- 0x13, 0x3b,
- 0x70, 0x43,
- 0xa6, 0x79, 0x56, 0x10, 0x6b, 0xb2, 0x88, 0xfb
- ];
-
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "We still use BinaryFormatter for WinForms drag and drop compatibility")]
- [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "We still use BinaryFormatter for WinForms drag and drop compatibility")]
- public static byte[]? TrySerializeUsingBinaryFormatter(object data, DataFormat dataFormat)
- {
- if (!OperatingSystemEx.IsWindows())
- return null;
-
- Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)?.Log(
- null,
- "Using BinaryFormatter to serialize data format {Format}. This won't be supported in Avalonia v12. Prefer passing a byte[] or Stream instead.",
- dataFormat);
-
- var stream = new MemoryStream();
- var serializedGuid = SerializedObjectGuid;
-
-#if NET6_0_OR_GREATER
- stream.Write(serializedGuid);
-#else
- stream.Write(serializedGuid.ToArray(), 0, serializedGuid.Length);
-#endif
-
-#pragma warning disable SYSLIB0011 // Type or member is obsolete
- new BinaryFormatter().Serialize(stream, data);
-#pragma warning restore SYSLIB0011 // Type or member is obsolete
-
- return stream.GetBuffer();
- }
-
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "We still use BinaryFormatter for WinForms drag and drop compatibility")]
- [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "We still use BinaryFormatter for WinForms drag and drop compatibility")]
- public static object? TryDeserializeUsingBinaryFormatter(byte[]? bytes)
- {
- var serializedObjectGuid = SerializedObjectGuid;
-
- // Our Win32 backend used to automatically serialize/deserialize objects using the BinaryFormatter.
- // Only keep that behavior for compatibility with IDataObject.
- if (OperatingSystemEx.IsWindows() && bytes is not null && bytes.AsSpan().StartsWith(serializedObjectGuid))
- {
- using var stream = new MemoryStream(bytes);
- stream.Position = serializedObjectGuid.Length;
-
-#pragma warning disable SYSLIB0011 // Type or member is obsolete
- return new BinaryFormatter().Deserialize(stream);
-#pragma warning restore SYSLIB0011 // Type or member is obsolete
- }
-
- return null;
- }
-}
diff --git a/src/Avalonia.Base/Input/Platform/Clipboard.cs b/src/Avalonia.Base/Input/Platform/Clipboard.cs
index 6810851775..c1ebf09196 100644
--- a/src/Avalonia.Base/Input/Platform/Clipboard.cs
+++ b/src/Avalonia.Base/Input/Platform/Clipboard.cs
@@ -1,9 +1,4 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Avalonia.Compatibility;
-using Avalonia.Platform.Storage;
+using System.Threading.Tasks;
namespace Avalonia.Input.Platform;
@@ -15,12 +10,6 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard
private readonly IClipboardImpl _clipboardImpl = clipboardImpl;
private IAsyncDataTransfer? _lastDataTransfer;
- Task IClipboard.GetTextAsync()
- => this.TryGetTextAsync();
-
- Task IClipboard.SetTextAsync(string? text)
- => this.SetValueAsync(DataFormat.Text, text);
-
public Task ClearAsync()
{
_lastDataTransfer?.Dispose();
@@ -29,10 +18,6 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard
return _clipboardImpl.ClearAsync();
}
- [Obsolete($"Use {nameof(SetDataAsync)} instead.")]
- Task IClipboard.SetDataObjectAsync(IDataObject data)
- => SetDataAsync(new DataObjectToDataTransferWrapper(data));
-
public Task SetDataAsync(IAsyncDataTransfer? dataTransfer)
{
if (dataTransfer is null)
@@ -47,49 +32,9 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard
public Task FlushAsync()
=> _clipboardImpl is IFlushableClipboardImpl flushable ? flushable.FlushAsync() : Task.CompletedTask;
- async Task IClipboard.GetFormatsAsync()
- {
- var dataTransfer = await TryGetDataAsync();
- return dataTransfer is null ? [] : dataTransfer.Formats.Select(DataFormats.ToString).ToArray();
- }
-
- [Obsolete($"Use {nameof(TryGetDataAsync)} instead.")]
- async Task IClipboard.GetDataAsync(string format)
- {
- // No ConfigureAwait(false) here: we want TryGetXxxAsync() below to be called on the initial thread.
- using var dataTransfer = await TryGetDataAsync();
- if (dataTransfer is null)
- return null;
-
- if (format == DataFormats.Text)
- return await dataTransfer.TryGetTextAsync().ConfigureAwait(false);
-
- if (format == DataFormats.Files)
- return await dataTransfer.TryGetFilesAsync().ConfigureAwait(false);
-
- if (format == DataFormats.FileNames)
- {
- return (await dataTransfer.TryGetFilesAsync().ConfigureAwait(false))
- ?.Select(file => file.TryGetLocalPath())
- .Where(path => path is not null)
- .ToArray();
- }
-
- var typedFormat = DataFormat.CreateBytesPlatformFormat(format);
- var bytes = await dataTransfer.TryGetValueAsync(typedFormat).ConfigureAwait(false);
- return BinaryFormatterHelper.TryDeserializeUsingBinaryFormatter(bytes) ?? bytes;
- }
-
public Task TryGetDataAsync()
=> _clipboardImpl.TryGetDataAsync();
- [Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")]
- async Task IClipboard.TryGetInProcessDataObjectAsync()
- {
- var dataTransfer = await TryGetInProcessDataAsync().ConfigureAwait(false);
- return (dataTransfer as DataObjectToDataTransferWrapper)?.DataObject;
- }
-
public async Task TryGetInProcessDataAsync()
{
if (_lastDataTransfer is null || _clipboardImpl is not IOwnedClipboardImpl ownedClipboardImpl)
diff --git a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs b/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs
deleted file mode 100644
index 656fbce996..0000000000
--- a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Runtime.Serialization.Formatters.Binary;
-using System.Text;
-using Avalonia.Compatibility;
-using Avalonia.Logging;
-
-namespace Avalonia.Input.Platform;
-
-///
-/// Wraps a legacy into a .
-///
-[Obsolete]
-internal sealed class DataObjectToDataTransferItemWrapper(
- IDataObject dataObject,
- DataFormat[] formats,
- string[] formatStrings)
- : PlatformDataTransferItem
-{
- private readonly IDataObject _dataObject = dataObject;
- private readonly DataFormat[] _formats = formats;
- private readonly string[] _formatStrings = formatStrings;
-
- protected override DataFormat[] ProvideFormats()
- => _formats;
-
- protected override object? TryGetRawCore(DataFormat format)
- {
- var index = Array.IndexOf(Formats, format);
- if (index < 0)
- return null;
-
- // We should never have DataFormat.File here, it's been handled by DataObjectToDataTransferWrapper.
- Debug.Assert(!DataFormat.File.Equals(format));
-
- var formatString = _formatStrings[index];
- var data = _dataObject.Get(formatString);
-
- if (DataFormat.Text.Equals(format))
- return Convert.ToString(data) ?? string.Empty;
-
- if (format is DataFormat)
- return Convert.ToString(data);
-
- if (format is DataFormat)
- return ConvertLegacyDataToBytes(format, data);
-
- return null;
- }
-
- private static byte[]? ConvertLegacyDataToBytes(DataFormat format, object? data)
- {
- switch (data)
- {
- case null:
- return null;
-
- case byte[] bytes:
- return bytes;
-
- case string str:
- return OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsIOS() ?
- Encoding.Unicode.GetBytes(str) :
- Encoding.UTF8.GetBytes(str);
-
- case Stream stream:
- var length = (int)(stream.Length - stream.Position);
- var buffer = new byte[length];
-
- stream.ReadExactly(buffer, 0, length);
- return buffer;
-
- default:
- return BinaryFormatterHelper.TrySerializeUsingBinaryFormatter(data, format);
- }
- }
-}
diff --git a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs b/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs
deleted file mode 100644
index 6fe64134d8..0000000000
--- a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using Avalonia.Platform.Storage;
-using Avalonia.Platform.Storage.FileIO;
-
-namespace Avalonia.Input.Platform;
-
-#pragma warning disable CS0618 // Type or member is obsolete: usages of IDataObject and DataFormats
-
-// TODO12: remove
-///
-/// Wraps a legacy into a .
-///
-[Obsolete]
-internal sealed class DataObjectToDataTransferWrapper(IDataObject dataObject)
- : PlatformDataTransfer
-{
- public IDataObject DataObject { get; } = dataObject;
-
- protected override DataFormat[] ProvideFormats()
- => DataObject.GetDataFormats().Select(DataFormats.ToDataFormat).Distinct().ToArray();
-
- protected override PlatformDataTransferItem[] ProvideItems()
- {
- var items = new List();
- var nonFileFormats = new List();
- var nonFileFormatStrings = new List();
- var hasFiles = false;
-
- foreach (var formatString in DataObject.GetDataFormats())
- {
- var format = DataFormats.ToDataFormat(formatString);
-
- if (formatString == DataFormats.Files)
- {
- if (hasFiles)
- continue;
-
- // This is not ideal as we're reading the filenames ahead of time to generate the appropriate items.
- // We don't really care about that for this legacy wrapper.
- if (DataObject.Get(formatString) is IEnumerable storageItems)
- {
- hasFiles = true;
-
- foreach (var storageItem in storageItems)
- items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem));
- }
- }
- else if (formatString == DataFormats.FileNames)
- {
- if (hasFiles)
- continue;
-
- if (DataObject.Get(formatString) is IEnumerable fileNames)
- {
- hasFiles = true;
-
- foreach (var fileName in fileNames)
- {
- if (StorageProviderHelpers.TryCreateBclStorageItem(fileName) is { } storageItem)
- items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem));
- }
- }
- }
- else
- {
- nonFileFormats.Add(format);
- nonFileFormatStrings.Add(formatString);
- }
- }
-
- if (nonFileFormats.Count > 0)
- {
- Debug.Assert(nonFileFormats.Count == nonFileFormatStrings.Count);
-
- // Single item containing all formats except for DataFormat.File.
- items.Add(new DataObjectToDataTransferItemWrapper(
- DataObject,
- nonFileFormats.ToArray(),
- nonFileFormatStrings.ToArray()));
- }
-
- return items.ToArray();
- }
-
- [SuppressMessage(
- "ReSharper",
- "SuspiciousTypeConversion.Global",
- Justification = "IDisposable may be implemented externally by the IDataObject instance.")]
- public override void Dispose()
- => (DataObject as IDisposable)?.Dispose();
-}
diff --git a/src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs b/src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs
deleted file mode 100644
index f1d6040efa..0000000000
--- a/src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Avalonia.Platform.Storage;
-
-namespace Avalonia.Input.Platform;
-
-///
-/// Wraps a into a legacy .
-///
-[Obsolete]
-internal sealed class DataTransferToDataObjectWrapper(IDataTransfer dataTransfer) : IDataObject
-{
- public IDataTransfer DataTransfer { get; } = dataTransfer;
-
- public IEnumerable GetDataFormats()
- => DataTransfer.Formats.Select(DataFormats.ToString);
-
- public bool Contains(string dataFormat)
- => DataTransfer.Contains(DataFormats.ToDataFormat(dataFormat));
-
- public object? Get(string dataFormat)
- {
- if (dataFormat == DataFormats.Text)
- return DataTransfer.TryGetText();
-
- if (dataFormat == DataFormats.Files)
- return DataTransfer.TryGetFiles();
-
- if (dataFormat == DataFormats.FileNames)
- {
- return DataTransfer
- .TryGetFiles()
- ?.Select(file => file.TryGetLocalPath())
- .Where(path => path is not null)
- .ToArray();
- }
-
- var typedFormat = DataFormat.CreateBytesPlatformFormat(dataFormat);
- var bytes = DataTransfer.TryGetValue(typedFormat);
- return BinaryFormatterHelper.TryDeserializeUsingBinaryFormatter(bytes) ?? bytes;
- }
-
-}
diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs
index b75e1b5a40..bc061be268 100644
--- a/src/Avalonia.Base/Input/Platform/IClipboard.cs
+++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs
@@ -1,4 +1,3 @@
-using System;
using System.Threading.Tasks;
using Avalonia.Metadata;
@@ -10,41 +9,11 @@ namespace Avalonia.Input.Platform
[NotClientImplementable]
public interface IClipboard
{
- // TODO12: remove, ClipboardExtensions.TryGetTextAsync exists
- ///
- /// Returns a string containing the text data on the clipboard.
- ///
- /// A string containing text data, or null if no corresponding text data is available.
- [Obsolete($"Use {nameof(ClipboardExtensions)}.{nameof(ClipboardExtensions.TryGetTextAsync)} instead")]
- Task GetTextAsync();
-
- // TODO12: remove, ClipboardExtensions.SetTextAsync exists
- ///
- /// Places a text on the clipboard.
- ///
- /// The text value to set.
- ///
- /// By calling this method, the clipboard will get cleared of any possible previous data.
- ///
- /// If is null or empty, nothing will get placed on the clipboard and this method
- /// will be equivalent to .
- ///
- ///
- Task SetTextAsync(string? text);
-
///
/// Clears any data from the system clipboard.
///
Task ClearAsync();
- ///
- /// Places a specified non-persistent data object on the system Clipboard.
- ///
- /// A data object (an object that implements ) to place on the system Clipboard.
- /// is null.
- [Obsolete($"Use {nameof(SetDataAsync)} instead.")]
- Task SetDataObjectAsync(IDataObject data);
-
///
/// Places a data object on the clipboard.
/// The data object is responsible for providing supported formats and data upon request.
@@ -69,20 +38,6 @@ namespace Avalonia.Input.Platform
/// This method is only supported on the Windows platform. This method will do nothing on other platforms.
Task FlushAsync();
- ///
- /// Get list of available Clipboard format.
- ///
- [Obsolete($"Use {nameof(ClipboardExtensions.GetDataFormatsAsync)} instead.")]
- Task GetFormatsAsync();
-
- ///
- /// Retrieves data in a specified format from the Clipboard.
- ///
- /// A string that specifies the format of the data to retrieve. For a set of predefined data formats, see the class.
- ///
- [Obsolete($"Use {nameof(TryGetDataAsync)} instead.")]
- Task GetDataAsync(string format);
-
///
/// Retrieves data from the clipboard.
///
@@ -95,16 +50,6 @@ namespace Avalonia.Input.Platform
///
Task TryGetDataAsync();
- ///
- /// If clipboard contains the IDataObject that was set by a previous call to ,
- /// return said IDataObject instance. Otherwise, return null.
- /// Note that not every platform supports that method, on unsupported platforms this method will always return
- /// null
- ///
- ///
- [Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")]
- Task TryGetInProcessDataObjectAsync();
-
///
/// Retrieves the exact instance of a previously placed on the clipboard
/// by , if any.
diff --git a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs
index 44881bcee1..4ad36576ca 100644
--- a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs
+++ b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Input.Platform
@@ -7,12 +6,6 @@ namespace Avalonia.Input.Platform
[NotClientImplementable]
public interface IPlatformDragSource
{
- [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")]
- Task DoDragDrop(
- PointerEventArgs triggerEvent,
- IDataObject data,
- DragDropEffects allowedEffects);
-
Task DoDragDropAsync(
PointerEventArgs triggerEvent,
IDataTransfer dataTransfer,
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index f243a2e382..94643fa91e 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -77,6 +77,7 @@ namespace Avalonia.Input
if (oldVisual != null)
oldVisual.DetachedFromVisualTree -= OnCaptureDetached;
Captured = control;
+ CaptureSource = source;
if (source != CaptureSource.Platform)
PlatformCapture(control);
@@ -115,6 +116,7 @@ namespace Avalonia.Input
public IInputElement? Captured { get; private set; }
public PointerType Type { get; }
+
public bool IsPrimary { get; }
///
@@ -124,6 +126,8 @@ namespace Avalonia.Input
public bool IsGestureRecognitionSkipped { get; set; }
+ internal CaptureSource CaptureSource { get; private set; } = CaptureSource.Platform;
+
public void Dispose()
{
if (Captured != null)
diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs
index 7682b0fb22..f4bf785c56 100644
--- a/src/Avalonia.Base/Input/PointerEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerEventArgs.cs
@@ -7,7 +7,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Input
{
- public class PointerEventArgs : RoutedEventArgs
+ public class PointerEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
private readonly Visual? _rootVisual;
private readonly Point _rootVisualPosition;
diff --git a/src/Avalonia.Base/Input/Raw/RawDragEvent.cs b/src/Avalonia.Base/Input/Raw/RawDragEvent.cs
index 5f7c5e3e04..9ba6f56cbb 100644
--- a/src/Avalonia.Base/Input/Raw/RawDragEvent.cs
+++ b/src/Avalonia.Base/Input/Raw/RawDragEvent.cs
@@ -1,40 +1,20 @@
-using System;
-using Avalonia.Input.Platform;
-using Avalonia.Metadata;
+using Avalonia.Metadata;
namespace Avalonia.Input.Raw
{
[PrivateApi]
public class RawDragEvent : RawInputEventArgs
{
- [Obsolete] private IDataObject? _legacyDataObject;
-
public Point Location { get; set; }
public IDataTransfer DataTransfer { get; }
- [Obsolete($"Use {nameof(DataTransfer)} instead.")]
- public IDataObject Data
- => _legacyDataObject ??= DataTransfer.ToLegacyDataObject();
-
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
public KeyModifiers KeyModifiers { get; }
- [Obsolete($"Use the constructor accepting a {nameof(IDataTransfer)} instance instead.")]
- public RawDragEvent(IDragDropDevice inputDevice,
- RawDragEventType type,
- IInputRoot root,
- Point location,
- IDataObject data,
- DragDropEffects effects,
- RawInputModifiers modifiers)
- : this(inputDevice, type, root, location, new DataObjectToDataTransferWrapper(data), effects, modifiers)
- {
- }
-
public RawDragEvent(
IDragDropDevice inputDevice,
RawDragEventType type,
diff --git a/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs
index 3995e7765c..cf006769e2 100644
--- a/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs
+++ b/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs
@@ -1,4 +1,3 @@
-using System;
using Avalonia.Metadata;
namespace Avalonia.Input.Raw
@@ -12,21 +11,6 @@ namespace Avalonia.Input.Raw
[PrivateApi]
public class RawKeyEventArgs : RawInputEventArgs
{
- [Obsolete("Use the overload that takes a physical key and key symbol instead.")]
- public RawKeyEventArgs(
- IKeyboardDevice device,
- ulong timestamp,
- IInputRoot root,
- RawKeyEventType type,
- Key key,
- RawInputModifiers modifiers)
- : this(device, timestamp, root, type, key, modifiers, PhysicalKey.None, KeyDeviceType.Keyboard, null)
- {
- Key = key;
- Type = type;
- Modifiers = modifiers;
- }
-
public RawKeyEventArgs(
IInputDevice device,
ulong timestamp,
@@ -35,24 +19,13 @@ namespace Avalonia.Input.Raw
Key key,
RawInputModifiers modifiers,
PhysicalKey physicalKey,
- string? keySymbol)
- : this(device, timestamp, root, type, key, modifiers, physicalKey, KeyDeviceType.Keyboard, keySymbol) { }
-
- public RawKeyEventArgs(
- IInputDevice device,
- ulong timestamp,
- IInputRoot root,
- RawKeyEventType type,
- Key key,
- RawInputModifiers modifiers,
- PhysicalKey physicalKey,
- KeyDeviceType keyDeviceType,
- string? keySymbol)
+ string? keySymbol,
+ KeyDeviceType keyDeviceType = KeyDeviceType.Keyboard)
: base(device, timestamp, root)
{
+ Type = type;
Key = key;
Modifiers = modifiers;
- Type = type;
PhysicalKey = physicalKey;
KeySymbol = keySymbol;
KeyDeviceType = keyDeviceType;
diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs
index 663207a104..eaffa1d8bc 100644
--- a/src/Avalonia.Base/Input/TappedEventArgs.cs
+++ b/src/Avalonia.Base/Input/TappedEventArgs.cs
@@ -4,7 +4,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Input
{
- public class TappedEventArgs : RoutedEventArgs
+ public class TappedEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
private readonly PointerEventArgs lastPointerEventArgs;
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/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 8adb3fb67e..9fa0f7689f 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -470,7 +470,10 @@ namespace Avalonia.Layout
}
}
- ///
+ ///
+ /// Called when a child control's desired size changes.
+ ///
+ /// The child control.
internal void ChildDesiredSizeChanged(Layoutable control)
{
if (!_measuring)
@@ -948,17 +951,5 @@ namespace Avalonia.Layout
{
return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0));
}
-
- internal override void SynchronizeCompositionProperties()
- {
- base.SynchronizeCompositionProperties();
-
- if (CompositionVisual is { } visual)
- {
- // If the visual isn't using layout rounding, it's possible that antialiasing renders to pixels
- // outside the current bounds. Extend the dirty rect by 1px in all directions in this case.
- visual.ShouldExtendDirtyRect = !UseLayoutRounding;
- }
- }
}
}
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/BitmapCache.cs b/src/Avalonia.Base/Media/BitmapCache.cs
new file mode 100644
index 0000000000..94caa1f557
--- /dev/null
+++ b/src/Avalonia.Base/Media/BitmapCache.cs
@@ -0,0 +1,114 @@
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+
+namespace Avalonia.Media;
+
+///
+/// Represents the behavior of caching a visual element or tree of elements as bitmap surfaces.
+///
+public class BitmapCache : CacheMode
+{
+ private CompositionBitmapCache? _current;
+
+ public static readonly StyledProperty RenderAtScaleProperty = AvaloniaProperty.Register(
+ nameof(RenderAtScale), 1);
+
+ ///
+ /// Use the RenderAtScale property to render the BitmapCache at a multiple of the normal bitmap size.
+ /// The normal size is determined by the local size of the element.
+ ///
+ /// Values greater than 1 increase the resolution of the bitmap relative to the native resolution of the element,
+ /// and values less than 1 decrease the resolution.
+ /// For example, if the RenderAtScale property is set to 2.0, and you apply a scale transform that
+ /// enlarges the content by a factor of 2, the content will have the same visual quality as the same content
+ /// with RenderAtScale set to 1.0 and a transform scale of 1.
+ ///
+ /// When RenderAtScale is set to 0, no bitmap is rendered. Negative values are clamped to 0.
+ ///
+ /// If you change this value, the cache is regenerated at the appropriate new resolution.
+ ///
+ public double RenderAtScale
+ {
+ get => GetValue(RenderAtScaleProperty);
+ set => SetValue(RenderAtScaleProperty, value);
+ }
+
+ public static readonly StyledProperty SnapsToDevicePixelsProperty = AvaloniaProperty.Register(
+ nameof(SnapsToDevicePixels));
+
+ ///
+ /// Set the SnapsToDevicePixels property when the cache displays content that requires pixel-alignment to render correctly.
+ /// This is the case for text with subpixel antialiasing. If you set the EnableClearType property to true,
+ /// consider setting SnapsToDevicePixels to true to ensure proper rendering.
+ ///
+ /// When the SnapsToDevicePixels property is set to false,
+ /// you can move and scale the cached element by a fraction of a pixel.
+ ///
+ /// When the SnapsToDevicePixels property is set to true,
+ /// the bitmap cache is aligned with pixel boundaries of the destination.
+ /// If you move or scale the cached element by a fraction of a pixel,
+ /// the bitmap snaps to the pixel grid
+ /// . In this case, the top-left corner of the bitmap is rounded up and snapped to the pixel grid,
+ /// but the bottom-right corner is on a fractional pixel boundary.
+ ///
+ public bool SnapsToDevicePixels
+ {
+ get => GetValue(SnapsToDevicePixelsProperty);
+ set => SetValue(SnapsToDevicePixelsProperty, value);
+ }
+
+ public static readonly StyledProperty EnableClearTypeProperty = AvaloniaProperty.Register(
+ nameof(EnableClearType));
+
+ ///
+ /// Set the EnableClearType property to allow subpixel text to be rendered in the cache.
+ /// When the EnableClearType property is true, your application MUST render all
+ /// of its subpixel text on an opaque background.
+ ///
+ /// When the EnableClearType property is false, text in the cache is rendered with grayscale antialiasing.
+ ///
+ /// ClearType text requires correct pixel alignment of rendered characters,
+ /// so you should set the SnapsToDevicePixels property to true.
+ /// If you do not set this property, the content may not blend correctly.
+ ///
+ /// Use the EnableClearType property when you know the cache is rendered on pixel boundaries,
+ /// so it is safe to cache ClearType text. This situation occurs commonly in text-scrolling scenarios.
+ ///
+ public bool EnableClearType
+ {
+ get => GetValue(EnableClearTypeProperty);
+ set => SetValue(EnableClearTypeProperty, value);
+ }
+
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if (change.IsEffectiveValueChange && _current != null)
+ {
+ if (change.Property == RenderAtScaleProperty)
+ _current.RenderAtScale = RenderAtScale;
+ else if (change.Property == SnapsToDevicePixelsProperty)
+ _current.SnapsToDevicePixels = SnapsToDevicePixels;
+ else if (change.Property == EnableClearTypeProperty)
+ _current.EnableClearType = EnableClearType;
+ }
+
+ base.OnPropertyChanged(change);
+ }
+
+ // We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
+ internal override CompositionCacheMode GetForCompositor(Compositor c)
+ {
+ // TODO: Make it to be a multi-compositor resource once we support visuals being attached to multiple
+ // compositor instances (e. g. referenced via visual brush from a different WASM toplevel).
+ if(_current?.Compositor != c)
+ {
+ _current = new CompositionBitmapCache(c, new ServerCompositionBitmapCache(c.Server));
+ _current.EnableClearType = EnableClearType;
+ _current.RenderAtScale = RenderAtScale;
+ _current.SnapsToDevicePixels = SnapsToDevicePixels;
+ }
+
+ return _current;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs
index 9ea2a992d7..7da504c14c 100644
--- a/src/Avalonia.Base/Media/BoxShadow.cs
+++ b/src/Avalonia.Base/Media/BoxShadow.cs
@@ -12,6 +12,8 @@ namespace Avalonia.Media
public struct BoxShadow
{
private readonly static char[] s_Separator = new char[] { ' ', '\t' };
+ private const char OpeningParenthesis = '(';
+ private const char ClosingParenthesis = ')';
///
/// Gets or sets the horizontal offset (distance) of the shadow.
@@ -208,7 +210,10 @@ namespace Avalonia.Media
throw new FormatException();
}
- var p = s.Split(s_Separator, StringSplitOptions.RemoveEmptyEntries);
+ var p = StringSplitter.SplitRespectingBrackets(
+ s, s_Separator,
+ OpeningParenthesis, ClosingParenthesis,
+ StringSplitOptions.RemoveEmptyEntries);
if (p.Length == 1 && p[0] == "none")
{
return default;
diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs
index 02e9d4279b..7a926b1191 100644
--- a/src/Avalonia.Base/Media/BoxShadows.cs
+++ b/src/Avalonia.Base/Media/BoxShadows.cs
@@ -9,7 +9,9 @@ namespace Avalonia.Media
///
public struct BoxShadows
{
- private static readonly char[] s_Separators = new[] { ',' };
+ private const char Separator = ',';
+ private const char OpeningParenthesis = '(';
+ private const char ClosingParenthesis = ')';
private readonly BoxShadow _first;
private readonly BoxShadow[]? _list;
@@ -120,7 +122,9 @@ namespace Avalonia.Media
/// A new collection.
public static BoxShadows Parse(string s)
{
- var sp = s.Split(s_Separators, StringSplitOptions.RemoveEmptyEntries);
+ var sp = StringSplitter.SplitRespectingBrackets(
+ s, Separator, OpeningParenthesis, ClosingParenthesis,
+ StringSplitOptions.RemoveEmptyEntries);
if (sp.Length == 0
|| (sp.Length == 1 &&
(string.IsNullOrWhiteSpace(sp[0])
@@ -236,7 +240,7 @@ namespace Avalonia.Media
///
/// true if the two collections are equal; otherwise, false.
///
- public static bool operator ==(BoxShadows left, BoxShadows right) =>
+ public static bool operator ==(BoxShadows left, BoxShadows right) =>
left.Equals(right);
///
diff --git a/src/Avalonia.Base/Media/CacheMode.cs b/src/Avalonia.Base/Media/CacheMode.cs
new file mode 100644
index 0000000000..44784eb29c
--- /dev/null
+++ b/src/Avalonia.Base/Media/CacheMode.cs
@@ -0,0 +1,21 @@
+using System;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Drawing;
+
+namespace Avalonia.Media;
+
+///
+/// Represents cached content modes for graphics acceleration features.
+///
+public abstract class CacheMode : StyledElement
+{
+ // We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
+ internal abstract CompositionCacheMode GetForCompositor(Compositor c);
+
+ public static CacheMode Parse(string s)
+ {
+ if(s == "BitmapCache")
+ return new BitmapCache();
+ throw new ArgumentException("Unknown CacheMode: " + s);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs
index ba487b1e80..ae16c30320 100644
--- a/src/Avalonia.Base/Media/Color.cs
+++ b/src/Avalonia.Base/Media/Color.cs
@@ -472,13 +472,6 @@ namespace Avalonia.Media
return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B;
}
- ///
- [Obsolete("Use Color.ToUInt32() instead."), EditorBrowsable(EditorBrowsableState.Never)]
- public uint ToUint32()
- {
- return ToUInt32();
- }
-
///
/// Returns the HSL color model equivalent of this RGB color.
///
diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs
index bd2c43878d..df6e7d112b 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,13 +420,20 @@ namespace Avalonia.Media
}
protected abstract void PushRenderOptionsCore(RenderOptions renderOptions);
- [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
- public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix);
- [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
- public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix);
- [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
- public PushedState PushTransformContainer() => PushTransform(Matrix.Identity);
-
+ ///
+ /// 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);
protected abstract void PushTransformCore(Matrix matrix);
@@ -433,6 +443,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/DrawingImage.cs b/src/Avalonia.Base/Media/DrawingImage.cs
index c83e8eb6ee..7949eaa351 100644
--- a/src/Avalonia.Base/Media/DrawingImage.cs
+++ b/src/Avalonia.Base/Media/DrawingImage.cs
@@ -22,6 +22,12 @@ namespace Avalonia.Media
public static readonly StyledProperty DrawingProperty =
AvaloniaProperty.Register(nameof(Drawing));
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ViewboxProperty =
+ AvaloniaProperty.Register(nameof(Viewbox));
+
///
public event EventHandler? Invalidated;
@@ -35,8 +41,25 @@ namespace Avalonia.Media
set => SetValue(DrawingProperty, value);
}
+ ///
+ /// Gets or sets a rectangular region of , in device independent pixels, to display
+ /// when rendering this image.
+ ///
+ ///
+ /// This value can be used to display only part of , or to surround it with empty
+ /// space. If null, will provide its own viewbox.
+ ///
+ ///
+ public Rect? Viewbox
+ {
+ get => GetValue(ViewboxProperty);
+ set => SetValue(ViewboxProperty, value);
+ }
+
///
- public Size Size => Drawing?.GetBounds().Size ?? default;
+ public Size Size => GetBounds().Size;
+
+ private Rect GetBounds() => Viewbox ?? Drawing?.GetBounds() ?? default;
///
void IImage.Draw(
@@ -44,14 +67,18 @@ namespace Avalonia.Media
Rect sourceRect,
Rect destRect)
{
- var drawing = Drawing;
+ if (Drawing is not { } drawing || sourceRect.Size == default || destRect.Size == default)
+ {
+ return;
+ }
+
+ var bounds = GetBounds();
- if (drawing == null)
+ if (bounds.Size == default)
{
return;
}
- var bounds = drawing.GetBounds();
var scale = Matrix.CreateScale(
destRect.Width / sourceRect.Width,
destRect.Height / sourceRect.Height);
@@ -62,7 +89,7 @@ namespace Avalonia.Media
using (context.PushClip(destRect))
using (context.PushTransform(translate * scale))
{
- Drawing?.Draw(context);
+ drawing.Draw(context);
}
}
@@ -71,7 +98,7 @@ namespace Avalonia.Media
{
base.OnPropertyChanged(change);
- if (change.Property == DrawingProperty)
+ if (change.Property == DrawingProperty || change.Property == ViewboxProperty)
{
RaiseInvalidated(EventArgs.Empty);
}
diff --git a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs
index 22d5a29870..24b80d01ea 100644
--- a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs
+++ b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs
@@ -78,7 +78,9 @@ namespace Avalonia.Media
public static readonly StyledProperty FallbackColorProperty =
AvaloniaProperty.Register(nameof(FallbackColor));
- ///
+ ///
+ /// Raised when any of the material's visual properties change.
+ ///
public event EventHandler? Invalidated;
///
@@ -110,7 +112,7 @@ namespace Avalonia.Media
///
/// Gets or Sets the Fallback Color.
- /// This is used on rendering plaforms that dont support acrylic.
+ /// This is used on rendering platforms that don't support acrylic.
///
public Color FallbackColor
{
diff --git a/src/Avalonia.Base/Media/FontFamily.cs b/src/Avalonia.Base/Media/FontFamily.cs
index aa2ce05bd4..a34321ef98 100644
--- a/src/Avalonia.Base/Media/FontFamily.cs
+++ b/src/Avalonia.Base/Media/FontFamily.cs
@@ -131,53 +131,55 @@ namespace Avalonia.Media
{
var result = new FrugalStructList(1);
- var segments = name.Split(',');
-
- for (int i = 0; i < segments.Length; i++)
+ int commaIndex = -1;
+ do
{
- var segment = segments[i];
- var innerSegments = segment.Split('#');
+ // Look for a comma separator to carve out a single segment.
+ int segmentStart = commaIndex + 1;
+ commaIndex = name.IndexOf(',', segmentStart);
+ int segmentEnd = commaIndex == -1
+ ? name.Length
+ : commaIndex;
+
+ var segment = name.AsSpan(segmentStart..segmentEnd).Trim();
- FontSourceIdentifier identifier = new FontSourceIdentifier(name, null);
+ FontSourceIdentifier? identifier = null;
- switch (innerSegments.Length)
+ // Check if there is exactly one '#' (i.e., segment is in the format "path#innerName").
+ int separatorIndex = segment.IndexOf('#');
+ if (separatorIndex != -1 && segment[(separatorIndex + 1)..].IndexOf('#') == -1)
{
- case 1:
+ var pathSpan = segment[..separatorIndex].Trim();
+ var innerName = segment[(separatorIndex + 1)..].Trim();
+
+ if (pathSpan.IsEmpty)
+ {
+ identifier = new FontSourceIdentifier(innerName.ToString(), null);
+ }
+ else
+ {
+ string path = pathSpan.ToString();
+ if (pathSpan.Contains('/') && Uri.TryCreate(path, UriKind.Relative, out var source))
{
- identifier = new FontSourceIdentifier(innerSegments[0].Trim(), null);
- break;
+ identifier = new FontSourceIdentifier(innerName.ToString(), source);
}
-
- case 2:
+ else
{
- var path = innerSegments[0].Trim();
- var innerName = innerSegments[1].Trim();
-
- if (string.IsNullOrEmpty(path))
- {
- identifier = new FontSourceIdentifier(innerName, null);
- }
- else
+ if (Uri.TryCreate(path, UriKind.Absolute, out source))
{
- if (path.Contains('/') && Uri.TryCreate(path, UriKind.Relative, out var source))
- {
- identifier = new FontSourceIdentifier(innerName, source);
- }
- else
- {
- if (Uri.TryCreate(path, UriKind.Absolute, out source))
- {
- identifier = new FontSourceIdentifier(innerName, source);
- }
- }
+ identifier = new FontSourceIdentifier(innerName.ToString(), source);
}
-
- break;
}
+ }
}
- result.Add(identifier);
- }
+ // If we didn't manage to match it to any known format, treat the entire segment as the font name.
+ identifier ??= new FontSourceIdentifier(
+ segment.Length == name.Length ? name : segment.ToString(),
+ null);
+
+ result.Add(identifier.Value);
+ } while (commaIndex != -1);
return result;
}
diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index 595a926f0c..9d1d0145d0 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -98,7 +98,7 @@ namespace Avalonia.Media
///
/// True, if the could create the glyph typeface, False otherwise.
///
- public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@@ -109,7 +109,7 @@ namespace Avalonia.Media
return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
}
-
+
if (fontFamily.Key != null)
{
if (fontFamily.Key is CompositeFontFamilyKey compositeKey)
@@ -187,7 +187,7 @@ namespace Avalonia.Media
}
}
- private bool TryGetGlyphTypefaceByKeyAndName(Typeface typeface, FontFamilyKey key, string familyName, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ private bool TryGetGlyphTypefaceByKeyAndName(Typeface typeface, FontFamilyKey key, string familyName, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
var source = key.Source.EnsureAbsolute(key.BaseUri);
@@ -271,7 +271,7 @@ namespace Avalonia.Media
{
typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
- if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.CharacterToGlyphMap.TryGetGlyph(codepoint, out _))
{
return true;
}
@@ -300,6 +300,11 @@ namespace Avalonia.Media
fontCollection.TryGetGlyphTypeface(familyName, fontStyle, fontWeight, fontStretch, out _) &&
fontCollection.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
{
+ if (typeface.FontFamily.Name == DefaultFontFamily.Name && i + 1 < compositeKey.Keys.Count)
+ {
+ continue;
+ }
+
return true;
}
}
@@ -328,24 +333,18 @@ namespace Avalonia.Media
if (key == null)
{
- if (SystemFonts is IFontCollection2 fontCollection2)
+ if (SystemFonts.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
{
- if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
- {
- return familyTypefaces;
- }
+ return familyTypefaces;
}
}
else
{
var source = key.Source.EnsureAbsolute(key.BaseUri);
- if (TryGetFontCollection(source, out var fontCollection) && fontCollection is IFontCollection2 fontCollection2)
+ if (TryGetFontCollection(source, out var fontCollection) && fontCollection.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
{
- if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
- {
- return familyTypefaces;
- }
+ return familyTypefaces;
}
}
@@ -374,7 +373,7 @@ namespace Avalonia.Media
fontCollection = new EmbeddedFontCollection(source, source);
}
}
-
+
if (fontCollection != null)
{
return _fontCollections.TryAdd(fontCollection.Key, fontCollection);
diff --git a/src/Avalonia.Base/Media/FontMetrics.cs b/src/Avalonia.Base/Media/FontMetrics.cs
index 6d952a6b93..1728c2ab44 100644
--- a/src/Avalonia.Base/Media/FontMetrics.cs
+++ b/src/Avalonia.Base/Media/FontMetrics.cs
@@ -8,7 +8,7 @@
///
/// Gets the font design units per em.
///
- public short DesignEmHeight { get; init; }
+ public ushort DesignEmHeight { get; init; }
///
/// A value indicating whether all glyphs in the font have the same advancement.
diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
index 8f2b43ff8d..4c535bdc0e 100644
--- a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
+++ b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
@@ -9,13 +9,13 @@ using Avalonia.Platform;
namespace Avalonia.Media.Fonts
{
- public abstract class FontCollectionBase : IFontCollection2
+ public abstract class FontCollectionBase : IFontCollection
{
private static readonly Comparer FontFamilyNameComparer =
Comparer.Create((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
// Make this internal for testing purposes
- internal readonly ConcurrentDictionary> _glyphTypefaceCache = new();
+ internal readonly ConcurrentDictionary> _glyphTypefaceCache = new();
private readonly object _fontFamiliesLock = new();
private volatile FontFamily[] _fontFamilies = Array.Empty();
@@ -39,12 +39,14 @@ namespace Avalonia.Media.Fonts
{
match = default;
+ var key = new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch };
+
//If a font family is defined we try to find a match inside that family first
if (familyName != null && _glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
{
- if (TryGetNearestMatch(glyphTypefaces, new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch }, out var glyphTypeface))
+ if (TryGetNearestMatch(glyphTypefaces, key, out var glyphTypeface))
{
- if (glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ if (glyphTypeface.CharacterToGlyphMap.TryGetGlyph(codepoint, out _))
{
match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName), style, weight, stretch);
@@ -64,15 +66,17 @@ namespace Avalonia.Media.Fonts
glyphTypefaces = pair.Value;
- if (TryGetNearestMatch(glyphTypefaces, new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch }, out var glyphTypeface))
+ if (TryGetNearestMatch(glyphTypefaces, key, out var glyphTypeface))
{
- if (glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ if (glyphTypeface.CharacterToGlyphMap.TryGetGlyph(codepoint, out _))
{
+ var platformTypeface = glyphTypeface.PlatformTypeface;
+
// Found a match
- match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName),
- glyphTypeface.Style,
- glyphTypeface.Weight,
- glyphTypeface.Stretch);
+ match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName),
+ platformTypeface.Style,
+ platformTypeface.Weight,
+ platformTypeface.Stretch);
return true;
}
@@ -83,11 +87,11 @@ namespace Avalonia.Media.Fonts
}
public virtual bool TryCreateSyntheticGlyphTypeface(
- IGlyphTypeface glyphTypeface,
+ GlyphTypeface glyphTypeface,
FontStyle style,
FontWeight weight,
FontStretch stretch,
- [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface)
+ [NotNullWhen(true)] out GlyphTypeface? syntheticGlyphTypeface)
{
syntheticGlyphTypeface = null;
@@ -99,44 +103,40 @@ namespace Avalonia.Media.Fonts
var key = new FontCollectionKey(style, weight, stretch);
- var currentKey =
- new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
-
+ var currentKey = glyphTypeface.ToFontCollectionKey();
+
if (currentKey == key)
{
return false;
}
- if (glyphTypeface is not IGlyphTypeface2 glyphTypeface2)
- {
- return false;
- }
-
var fontSimulations = FontSimulations.None;
- if (style != FontStyle.Normal && glyphTypeface2.Style != style)
+ if (style != FontStyle.Normal && glyphTypeface.Style != style)
{
fontSimulations |= FontSimulations.Oblique;
}
- if ((int)weight >= 600 && glyphTypeface2.Weight < weight)
+ if ((int)weight >= 600 && glyphTypeface.Weight < weight)
{
fontSimulations |= FontSimulations.Bold;
}
- if (fontSimulations != FontSimulations.None && glyphTypeface2.TryGetStream(out var stream))
+ if (fontSimulations != FontSimulations.None && glyphTypeface.PlatformTypeface.TryGetStream(out var stream))
{
using (stream)
{
- if (_fontManagerImpl.TryCreateGlyphTypeface(stream, fontSimulations, out syntheticGlyphTypeface))
+ if (_fontManagerImpl.TryCreateGlyphTypeface(stream, fontSimulations, out var platformTypeface))
{
+ syntheticGlyphTypeface = new GlyphTypeface(platformTypeface, fontSimulations);
+
//Add the TypographicFamilyName to the cache
- if (!string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
+ if (!string.IsNullOrEmpty(glyphTypeface.TypographicFamilyName))
{
- TryAddGlyphTypeface(glyphTypeface2.TypographicFamilyName, key, syntheticGlyphTypeface);
+ TryAddGlyphTypeface(glyphTypeface.TypographicFamilyName, key, syntheticGlyphTypeface);
}
- foreach (var kvp in glyphTypeface2.FamilyNames)
+ foreach (var kvp in glyphTypeface.FamilyNames)
{
TryAddGlyphTypeface(kvp.Value, key, syntheticGlyphTypeface);
}
@@ -154,17 +154,11 @@ namespace Avalonia.Media.Fonts
public IEnumerator GetEnumerator() => ((IEnumerable)_fontFamilies).GetEnumerator();
public virtual bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
- FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ FontStretch stretch, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
var typeface = new Typeface(familyName, style, weight, stretch).Normalize(out familyName);
- style = typeface.Style;
-
- weight = typeface.Weight;
-
- stretch = typeface.Stretch;
-
- var key = new FontCollectionKey(style, weight, stretch);
+ var key = typeface.ToFontCollectionKey();
return TryGetGlyphTypeface(familyName, key, out glyphTypeface);
}
@@ -195,7 +189,7 @@ namespace Avalonia.Media.Fonts
return false;
}
- public bool TryGetNearestMatch(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ public bool TryGetNearestMatch(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
if (!_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
{
@@ -210,52 +204,59 @@ namespace Avalonia.Media.Fonts
}
///
- /// Attempts to add the specified to the font collection.
+ /// Attempts to add the specified to the font collection.
///
- /// This method checks the and, if applicable,
- /// the typographic family name and other family names provided by the interface.
+ /// This method checks the and, if applicable,
+ /// the typographic family name and other family names provided by the interface.
/// If any of these names can be associated with the glyph typeface, the typeface is added to the collection.
/// The method ensures that duplicate entries are not added.
/// The glyph typeface to add. Must not be and must have a non-empty .
+ /// cref="GlyphTypeface.FamilyName"/>.
/// if the glyph typeface was successfully added to the collection; otherwise, .
- public bool TryAddGlyphTypeface(IGlyphTypeface glyphTypeface)
+ public bool TryAddGlyphTypeface(GlyphTypeface glyphTypeface)
+ {
+ var key = glyphTypeface.ToFontCollectionKey();
+
+ return TryAddGlyphTypeface(glyphTypeface, key);
+ }
+
+ ///
+ /// Attempts to add the specified glyph typeface to the collection using the provided key.
+ ///
+ /// The method adds the glyph typeface using both its typographic family name and all
+ /// available family names. If the glyph typeface or its family name is invalid, the method returns false and
+ /// does not add the typeface.
+ /// The glyph typeface to add. Cannot be null, and its FamilyName property must not be null or empty.
+ /// The key that identifies the font collection to which the glyph typeface will be added.
+ /// true if the glyph typeface was successfully added to the collection; otherwise, false.
+ public bool TryAddGlyphTypeface(GlyphTypeface glyphTypeface, FontCollectionKey key)
{
if (glyphTypeface == null || string.IsNullOrEmpty(glyphTypeface.FamilyName))
{
return false;
}
- var key = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
+ var result = false;
- if (glyphTypeface is IGlyphTypeface2 glyphTypeface2)
+ //Add the TypographicFamilyName to the cache
+ if (!string.IsNullOrEmpty(glyphTypeface.TypographicFamilyName))
{
- var result = false;
-
- //Add the TypographicFamilyName to the cache
- if (!string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
+ if (TryAddGlyphTypeface(glyphTypeface.TypographicFamilyName, key, glyphTypeface))
{
- if (TryAddGlyphTypeface(glyphTypeface2.TypographicFamilyName, key, glyphTypeface))
- {
- result = true;
- }
+ result = true;
}
+ }
- foreach (var kvp in glyphTypeface2.FamilyNames)
+ foreach (var kvp in glyphTypeface.FamilyNames)
+ {
+ if (TryAddGlyphTypeface(kvp.Value, key, glyphTypeface))
{
- if (TryAddGlyphTypeface(kvp.Value, key, glyphTypeface))
- {
- result = true;
- }
+ result = true;
}
-
- return result;
- }
- else
- {
- return TryAddGlyphTypeface(glyphTypeface.FamilyName, key, glyphTypeface);
}
+
+ return result;
}
///
@@ -265,17 +266,21 @@ namespace Avalonia.Media.Fonts
/// If successful, it adds the created glyph typeface to the collection.
/// The font stream containing the font data. The stream must be readable and positioned at the beginning of the
/// font data.
- /// When this method returns, contains the created instance if the operation
+ /// When this method returns, contains the created instance if the operation
/// succeeds; otherwise, .
/// if the glyph typeface was successfully created and added; otherwise, .
- public bool TryAddGlyphTypeface(Stream stream, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ public bool TryAddGlyphTypeface(Stream stream, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
- if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out glyphTypeface))
+ glyphTypeface = null;
+
+ if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
{
return false;
}
+ glyphTypeface = new GlyphTypeface(platformTypeface);
+
return TryAddGlyphTypeface(glyphTypeface);
}
@@ -310,17 +315,19 @@ namespace Avalonia.Media.Fonts
{
var stream = _assetLoader.Open(fontAsset);
- if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
+ if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
{
continue;
}
- var key = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
+ var glyphTypeface = new GlyphTypeface(platformTypeface);
+
+ var key = glyphTypeface.ToFontCollectionKey();
//Add TypographicFamilyName to the cache
- if (glyphTypeface is IGlyphTypeface2 glyphTypeface2 && !string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
+ if (!string.IsNullOrEmpty(glyphTypeface.TypographicFamilyName))
{
- if (TryAddGlyphTypeface(glyphTypeface2.TypographicFamilyName, key, glyphTypeface))
+ if (TryAddGlyphTypeface(glyphTypeface.TypographicFamilyName, key, glyphTypeface))
{
result = true;
}
@@ -346,8 +353,10 @@ namespace Avalonia.Media.Fonts
using var stream = File.OpenRead(source.LocalPath);
- if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
+ if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
{
+ var glyphTypeface = new GlyphTypeface(platformTypeface);
+
if (TryAddGlyphTypeface(glyphTypeface))
{
result = true;
@@ -368,8 +377,10 @@ namespace Avalonia.Media.Fonts
{
using var stream = File.OpenRead(file);
- if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
+ if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
{
+ var glyphTypeface = new GlyphTypeface(platformTypeface);
+
if (TryAddGlyphTypeface(glyphTypeface))
{
result = true;
@@ -445,10 +456,10 @@ namespace Avalonia.Media.Fonts
/// find the best match based on the provided .
/// The name of the font family to search for. This parameter is case-insensitive.
/// The key representing the desired font collection attributes.
- /// When this method returns, contains the matching if a match is found; otherwise,
+ /// When this method returns, contains the matching if a match is found; otherwise,
/// .
/// if a matching glyph typeface is found; otherwise, .
- protected bool TryGetGlyphTypeface(string familyName, FontCollectionKey key, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ protected bool TryGetGlyphTypeface(string familyName, FontCollectionKey key, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@@ -461,7 +472,7 @@ namespace Avalonia.Media.Fonts
if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
{
- var matchedKey = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
+ var matchedKey = glyphTypeface.ToFontCollectionKey();
if (matchedKey != key)
{
@@ -550,19 +561,21 @@ namespace Avalonia.Media.Fonts
}
///
- /// Attempts to retrieve the nearest matching for the specified font key from the
+ /// Attempts to retrieve the nearest matching for the specified font key from the
/// provided collection of glyph typefaces.
///
/// This method attempts to find the best match for the specified font key by considering
- /// various fallback strategies, such as normalizing the font style, stretch, and weight. If no suitable match is found, the method will return the first available non-null from the
+ /// various fallback strategies, such as normalizing the font style, stretch, and weight.
+ /// If no suitable match is found, the method will return the first available non-null from the
/// collection, if any.
/// A collection of glyph typefaces, indexed by .
/// The representing the desired font attributes.
- /// When this method returns, contains the that most closely matches the specified
+ /// When this method returns, contains the that most closely matches the specified
/// key, if a match is found; otherwise, .
- /// if a matching is found; otherwise, if a matching is found; otherwise, .
- protected bool TryGetNearestMatch(IDictionary glyphTypefaces, FontCollectionKey key, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ protected bool TryGetNearestMatch(IDictionary glyphTypefaces,
+ FontCollectionKey key, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
{
@@ -627,7 +640,7 @@ namespace Avalonia.Media.Fonts
/// The glyph typeface to add to the cache. Can be null.
/// if the glyph typeface was successfully added to the cache; otherwise, .
- protected bool TryAddGlyphTypeface(string familyName, FontCollectionKey key, IGlyphTypeface? glyphTypeface)
+ protected bool TryAddGlyphTypeface(string familyName, FontCollectionKey key, GlyphTypeface? glyphTypeface)
{
if (string.IsNullOrEmpty(familyName))
{
@@ -651,7 +664,7 @@ namespace Avalonia.Media.Fonts
}
// Family doesn't exist yet. Create a new dictionary instance and try to install it.
- var newDict = new ConcurrentDictionary();
+ var newDict = new ConcurrentDictionary();
// GetOrAdd will return the instance that ended up in the dictionary. If it's our
// newDict instance then we won the race to add the family and should publish it.
@@ -693,9 +706,9 @@ namespace Avalonia.Media.Fonts
/// null.
/// true if a suitable fallback glyph typeface is found; otherwise, false.
private static bool TryFindStretchFallback(
- IDictionary glyphTypefaces,
+ IDictionary glyphTypefaces,
FontCollectionKey key,
- [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@@ -740,9 +753,9 @@ namespace Avalonia.Media.Fonts
/// null.
/// true if a fallback glyph typeface matching the requested weight is found; otherwise, false.
private static bool TryFindWeightFallback(
- IDictionary glyphTypefaces,
+ IDictionary glyphTypefaces,
FontCollectionKey key,
- [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
var weight = (int)key.Weight;
diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs
index 0d0dc3016e..c9a1264c24 100644
--- a/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs
+++ b/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs
@@ -1,4 +1,12 @@
namespace Avalonia.Media.Fonts
{
+ ///
+ /// Represents a unique key for identifying a font inside a font collection based on style, weight, and stretch attributes.
+ ///
+ /// Use this key to efficiently look up or group fonts in a collection by their style, weight,
+ /// and stretch characteristics.
+ /// The font style to use when constructing the key.
+ /// The font weight to use when constructing the key.
+ /// The font stretch to use when constructing the key.
public readonly record struct FontCollectionKey(FontStyle Style, FontWeight Weight, FontStretch Stretch);
}
diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionKeyExtensions.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionKeyExtensions.cs
new file mode 100644
index 0000000000..5d583e175c
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/FontCollectionKeyExtensions.cs
@@ -0,0 +1,50 @@
+using System;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Fonts
+{
+ internal static class FontCollectionKeyExtensions
+ {
+ ///
+ /// Creates a new FontCollectionKey based on the style, weight, and stretch of the specified Typeface.
+ ///
+ /// The Typeface from which to extract style, weight, and stretch information. Cannot be null.
+ /// A FontCollectionKey representing the style, weight, and stretch of the specified Typeface.
+ public static FontCollectionKey ToFontCollectionKey(this Typeface typeface)
+ {
+ return new FontCollectionKey(typeface.Style, typeface.Weight, typeface.Stretch);
+ }
+
+ ///
+ /// Creates a new FontCollectionKey based on the style, weight, and stretch of the specified GlyphTypeface.
+ ///
+ /// The GlyphTypeface instance from which to extract style, weight, and stretch information. Cannot be null.
+ /// A FontCollectionKey representing the style, weight, and stretch of the specified glyph typeface.
+ /// Thrown if glyphTypeface is null.
+ public static FontCollectionKey ToFontCollectionKey(this GlyphTypeface glyphTypeface)
+ {
+ if (glyphTypeface == null)
+ {
+ throw new ArgumentNullException(nameof(glyphTypeface));
+ }
+
+ return new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
+ }
+
+ ///
+ /// Creates a new FontCollectionKey based on the style, weight, and stretch of the specified platform typeface.
+ ///
+ /// The platform typeface from which to extract style, weight, and stretch information. Cannot be null.
+ /// A FontCollectionKey representing the style, weight, and stretch of the specified platform typeface.
+ /// Thrown if platformTypeface is null.
+ public static FontCollectionKey ToFontCollectionKey(this IPlatformTypeface platformTypeface)
+ {
+ if (platformTypeface == null)
+ {
+ throw new ArgumentNullException(nameof(platformTypeface));
+ }
+
+ return new FontCollectionKey(platformTypeface.Style, platformTypeface.Weight, platformTypeface.Stretch);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
index 4579cb5a34..17d1ef7f21 100644
--- a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
@@ -5,6 +5,12 @@ using System.Globalization;
namespace Avalonia.Media.Fonts
{
+ ///
+ /// Represents a collection of font families and provides methods for querying and managing font typefaces
+ /// within the collection.
+ ///
+ /// Implementations of this interface allow applications to retrieve font families, match
+ /// characters to typefaces, and obtain glyph typefaces based on specific font properties.
public interface IFontCollection : IReadOnlyList, IDisposable
{
///
@@ -22,7 +28,7 @@ namespace Avalonia.Media.Fonts
/// The glyph typeface.
/// Returns true if a glyph typface can be found; otherwise, false
bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
- FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
+ FontStretch stretch, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface);
///
/// Tries to match a specified character to a that supports specified font properties.
@@ -39,17 +45,14 @@ namespace Avalonia.Media.Fonts
///
bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface typeface);
- }
- internal interface IFontCollection2 : IFontCollection
- {
///
/// Tries to get a list of typefaces for the specified family name.
///
/// The family name.
/// The list of typefaces.
///
- /// True, if the could get the list of typefaces, False otherwise.
+ /// True, if the could get the list of typefaces, False otherwise.
///
bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces);
@@ -62,7 +65,8 @@ namespace Avalonia.Media.Fonts
/// The font stretch.
///
/// Returns true if a synthetic glyph typface can be created; otherwise, false
- bool TryCreateSyntheticGlyphTypeface(IGlyphTypeface glyphTypeface, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface);
+ bool TryCreateSyntheticGlyphTypeface(GlyphTypeface glyphTypeface, FontStyle style, FontWeight weight, FontStretch stretch,
+ [NotNullWhen(true)] out GlyphTypeface? syntheticGlyphTypeface);
///
/// Attempts to retrieve the glyph typeface that most closely matches the specified font family name, style,
@@ -77,9 +81,10 @@ namespace Avalonia.Media.Fonts
/// The desired font style.
/// The desired font weight.
/// The desired font stretch.
- /// When this method returns, contains the that most closely matches the specified
+ /// When this method returns, contains the that most closely matches the specified
/// parameters, if a match is found; otherwise, . This parameter is passed uninitialized.
/// if a matching glyph typeface is found; otherwise, .
- bool TryGetNearestMatch(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
+ bool TryGetNearestMatch(string familyName, FontStyle style, FontWeight weight, FontStretch stretch,
+ [NotNullWhen(true)] out GlyphTypeface? glyphTypeface);
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/OpenTypeTag.cs b/src/Avalonia.Base/Media/Fonts/OpenTypeTag.cs
index b0c725ca92..0a50cc6f13 100644
--- a/src/Avalonia.Base/Media/Fonts/OpenTypeTag.cs
+++ b/src/Avalonia.Base/Media/Fonts/OpenTypeTag.cs
@@ -2,11 +2,11 @@
namespace Avalonia.Media.Fonts
{
- internal readonly record struct OpenTypeTag
+ public readonly record struct OpenTypeTag
{
- public static readonly OpenTypeTag None = new OpenTypeTag(0, 0, 0, 0);
- public static readonly OpenTypeTag Max = new OpenTypeTag(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
- public static readonly OpenTypeTag MaxSigned = new OpenTypeTag((byte)sbyte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
+ internal static readonly OpenTypeTag None = new OpenTypeTag(0, 0, 0, 0);
+ internal static readonly OpenTypeTag Max = new OpenTypeTag(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
+ internal static readonly OpenTypeTag MaxSigned = new OpenTypeTag((byte)sbyte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
private readonly uint _value;
diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
index 7ff8df9951..cf055e5d99 100644
--- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Media.Fonts
public override Uri Key => FontManager.SystemFontsKey;
public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
- FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ FontStretch stretch, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
var typeface = new Typeface(familyName, style, weight, stretch).Normalize(out familyName);
@@ -35,13 +35,7 @@ namespace Avalonia.Media.Fonts
return true;
}
- style = typeface.Style;
-
- weight = typeface.Weight;
-
- stretch = typeface.Stretch;
-
- var key = new FontCollectionKey(style, weight, stretch);
+ var key = typeface.ToFontCollectionKey();
//Check cache first to avoid unnecessary calls to the font manager
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces) && glyphTypefaces.TryGetValue(key, out glyphTypeface))
@@ -50,7 +44,7 @@ namespace Avalonia.Media.Fonts
}
//Try to create the glyph typeface via system font manager
- if (!_platformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
+ if (!_platformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out var platformTypeface))
{
//Add null to cache to avoid future calls
TryAddGlyphTypeface(familyName, key, null);
@@ -58,9 +52,23 @@ namespace Avalonia.Media.Fonts
return false;
}
+ glyphTypeface = new GlyphTypeface(platformTypeface);
+
+ //Add to cache with platform typeface family name first
+ TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface);
+
//Add to cache
if (!TryAddGlyphTypeface(glyphTypeface))
{
+ // Another thread may have added an entry for this key while we were creating the glyph typeface.
+ // Re-check the cache and yield the existing glyph typeface if present.
+ if (_glyphTypefaceCache.TryGetValue(familyName, out var existingMap) && existingMap.TryGetValue(key, out var existingTypeface) && existingTypeface != null)
+ {
+ glyphTypeface = existingTypeface;
+
+ return true;
+ }
+
return false;
}
@@ -70,14 +78,7 @@ namespace Avalonia.Media.Fonts
public override bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces)
{
- familyTypefaces = null;
-
- if (_platformImpl is IFontManagerImpl2 fontManagerImpl2)
- {
- return fontManagerImpl2.TryGetFamilyTypefaces(familyName, out familyTypefaces);
- }
-
- return false;
+ return _platformImpl.TryGetFamilyTypefaces(familyName, out familyTypefaces);
}
public override bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch, string? familyName,
@@ -85,10 +86,9 @@ namespace Avalonia.Media.Fonts
{
var requestedKey = new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch };
- //TODO12: Think about removing familyName parameter
if (base.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match))
{
- var matchKey = new FontCollectionKey { Style = match.Style, Weight = match.Weight, Stretch = match.Stretch };
+ var matchKey = match.ToFontCollectionKey();
if (requestedKey == matchKey)
{
@@ -96,25 +96,43 @@ namespace Avalonia.Media.Fonts
}
}
- if (_platformImpl is IFontManagerImpl2 fontManagerImpl2)
+ if (_platformImpl.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out var platformTypeface))
{
- if (fontManagerImpl2.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out var glyphTypeface))
+ // Construct the resulting Typeface
+ match = new Typeface(platformTypeface.FamilyName, platformTypeface.Style, platformTypeface.Weight,
+ platformTypeface.Stretch);
+
+ // Compute the key for cache lookup this can be different from the requested key
+ var key = match.ToFontCollectionKey();
+
+ // Check cache first: if an entry exists and is non-null, match succeeded and we can return true.
+ if (_glyphTypefaceCache.TryGetValue(platformTypeface.FamilyName, out var glyphTypefaces) && glyphTypefaces.TryGetValue(key, out var existing))
{
- match = new Typeface(glyphTypeface.FamilyName, glyphTypeface.Style, glyphTypeface.Weight,
- glyphTypeface.Stretch);
+ return existing != null;
+ }
- // Add to cache if not already present
- TryAddGlyphTypeface(glyphTypeface);
+ // Not in cache yet: create glyph typeface and try to add it.
+ var glyphTypeface = new GlyphTypeface(platformTypeface);
+ // Try adding with the platform typeface family name first.
+ TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface);
+
+ // Try adding the glyph typeface with the matched key.
+ if (TryAddGlyphTypeface(glyphTypeface, key))
+ {
return true;
}
+ // TryAddGlyphTypeface failed: another thread may have added an entry. Re-check the cache.
+ if (_glyphTypefaceCache.TryGetValue(platformTypeface.FamilyName, out glyphTypefaces) && glyphTypefaces.TryGetValue(key, out existing))
+ {
+ return existing != null;
+ }
+
return false;
}
- else
- {
- return _platformImpl.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match);
- }
+
+ return false;
}
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/BigEndianBinaryReader.cs b/src/Avalonia.Base/Media/Fonts/Tables/BigEndianBinaryReader.cs
index bca46b7e8c..58d162847f 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/BigEndianBinaryReader.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/BigEndianBinaryReader.cs
@@ -5,104 +5,92 @@
using System;
using System.Buffers.Binary;
using System.Diagnostics;
-using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace Avalonia.Media.Fonts.Tables
{
///
- /// BinaryReader using big-endian encoding.
+ /// BinaryReader using big-endian encoding for ReadOnlySpan<byte>.
///
- [DebuggerDisplay("Start: {StartOfStream}, Position: {BaseStream.Position}")]
- internal class BigEndianBinaryReader : IDisposable
+ [DebuggerDisplay("Start: {StartOfSpan}, Position: {Position}")]
+ internal ref struct BigEndianBinaryReader
{
- ///
- /// Buffer used for temporary storage before conversion into primitives
- ///
- private readonly byte[] _buffer = new byte[16];
-
- private readonly bool _leaveOpen;
+ private readonly ReadOnlySpan _span;
+ private int _position;
+ private readonly int _startOfSpan;
///
/// Initializes a new instance of the class.
- /// Constructs a new binary reader with the given bit converter, reading
- /// to the given stream, using the given encoding.
///
- /// Stream to read data from
- /// if set to true [leave open].
- public BigEndianBinaryReader(Stream stream, bool leaveOpen)
+ /// Span to read data from
+ public BigEndianBinaryReader(ReadOnlySpan span)
{
- BaseStream = stream;
- StartOfStream = stream.Position;
- _leaveOpen = leaveOpen;
+ _span = span;
+ _position = 0;
+ _startOfSpan = 0;
}
- private long StartOfStream { get; }
+ private readonly int StartOfSpan => _startOfSpan;
///
- /// Gets the underlying stream of the EndianBinaryReader.
+ /// Gets the current position in the span.
///
- public Stream BaseStream { get; }
+ public readonly int Position => _position;
///
- /// Seeks within the stream.
+ /// Seeks within the span.
///
/// Offset to seek to.
- /// Origin of seek operation. If SeekOrigin.Begin, the offset will be set to the start of stream position.
- public void Seek(long offset, SeekOrigin origin)
+ public void Seek(int offset)
{
- // If SeekOrigin.Begin, the offset will be set to the start of stream position.
- if (origin == SeekOrigin.Begin)
+ int absoluteOffset = _startOfSpan + offset;
+
+ if (offset < 0 || absoluteOffset > _span.Length)
{
- offset += StartOfStream;
+ throw new ArgumentOutOfRangeException(nameof(offset));
}
- BaseStream.Seek(offset, origin);
+ _position = absoluteOffset;
}
- ///
- /// Reads a single byte from the stream.
- ///
- /// The byte read
public byte ReadByte()
{
- ReadInternal(_buffer, 1);
- return _buffer[0];
+ EnsureAvailable(1);
+
+ return _span[_position++];
}
- ///
- /// Reads a single signed byte from the stream.
- ///
- /// The byte read
public sbyte ReadSByte()
{
- ReadInternal(_buffer, 1);
- return unchecked((sbyte)_buffer[0]);
+ EnsureAvailable(1);
+
+ return unchecked((sbyte)_span[_position++]);
}
public float ReadF2dot14()
{
const float f2Dot14ToFloat = 16384.0f;
+
return ReadInt16() / f2Dot14ToFloat;
}
- ///
- /// Reads a 16-bit signed integer from the stream, using the bit converter
- /// for this reader. 2 bytes are read.
- ///
- /// The 16-bit integer read
public short ReadInt16()
{
- ReadInternal(_buffer, 2);
+ EnsureAvailable(2);
+
+ short value = BinaryPrimitives.ReadInt16BigEndian(_span.Slice(_position, 2));
+
+ _position += 2;
- return BinaryPrimitives.ReadInt16BigEndian(_buffer);
+ return value;
}
public TEnum ReadInt16()
where TEnum : struct, Enum
{
TryConvert(ReadUInt16(), out TEnum value);
+
return value;
}
@@ -112,77 +100,75 @@ namespace Avalonia.Media.Fonts.Tables
public ushort ReadUFWORD() => ReadUInt16();
- ///
- /// Reads a fixed 32-bit value from the stream.
- /// 4 bytes are read.
- ///
- /// The 32-bit value read.
public float ReadFixed()
{
- ReadInternal(_buffer, 4);
- return BinaryPrimitives.ReadInt32BigEndian(_buffer) / 65536F;
+ EnsureAvailable(4);
+
+ float value = BinaryPrimitives.ReadInt32BigEndian(_span.Slice(_position, 4)) / 65536F;
+
+ _position += 4;
+
+ return value;
+ }
+
+ public FontVersion ReadVersion16Dot16()
+ {
+ EnsureAvailable(4);
+
+ uint value = BinaryPrimitives.ReadUInt32BigEndian(_span.Slice(_position, 4));
+
+ _position += 4;
+
+ return new FontVersion(value);
}
- ///
- /// Reads a 32-bit signed integer from the stream, using the bit converter
- /// for this reader. 4 bytes are read.
- ///
- /// The 32-bit integer read
public int ReadInt32()
{
- ReadInternal(_buffer, 4);
+ EnsureAvailable(4);
+
+ int value = BinaryPrimitives.ReadInt32BigEndian(_span.Slice(_position, 4));
- return BinaryPrimitives.ReadInt32BigEndian(_buffer);
+ _position += 4;
+
+ return value;
}
- ///
- /// Reads a 64-bit signed integer from the stream.
- /// 8 bytes are read.
- ///
- /// The 64-bit integer read.
public long ReadInt64()
{
- ReadInternal(_buffer, 8);
+ EnsureAvailable(8);
+
+ long value = BinaryPrimitives.ReadInt64BigEndian(_span.Slice(_position, 8));
+
+ _position += 8;
- return BinaryPrimitives.ReadInt64BigEndian(_buffer);
+ return value;
}
- ///
- /// Reads a 16-bit unsigned integer from the stream.
- /// 2 bytes are read.
- ///
- /// The 16-bit unsigned integer read.
public ushort ReadUInt16()
{
- ReadInternal(_buffer, 2);
+ EnsureAvailable(2);
- return BinaryPrimitives.ReadUInt16BigEndian(_buffer);
+ ushort value = BinaryPrimitives.ReadUInt16BigEndian(_span.Slice(_position, 2));
+
+ _position += 2;
+
+ return value;
}
- ///
- /// Reads a 16-bit unsigned integer from the stream representing an offset position.
- /// 2 bytes are read.
- ///
- /// The 16-bit unsigned integer read.
public ushort ReadOffset16() => ReadUInt16();
public TEnum ReadUInt16()
where TEnum : struct, Enum
{
TryConvert(ReadUInt16(), out TEnum value);
+
return value;
}
- ///
- /// Reads array of 16-bit unsigned integers from the stream.
- ///
- /// The length.
- ///
- /// The 16-bit unsigned integer read.
- ///
public ushort[] ReadUInt16Array(int length)
{
ushort[] data = new ushort[length];
+
for (int i = 0; i < length; i++)
{
data[i] = ReadUInt16();
@@ -191,10 +177,6 @@ namespace Avalonia.Media.Fonts.Tables
return data;
}
- ///
- /// Reads array of 16-bit unsigned integers from the stream to the buffer.
- ///
- /// The buffer to read to.
public void ReadUInt16Array(Span buffer)
{
for (int i = 0; i < buffer.Length; i++)
@@ -203,16 +185,10 @@ namespace Avalonia.Media.Fonts.Tables
}
}
- ///
- /// Reads array or 32-bit unsigned integers from the stream.
- ///
- /// The length.
- ///
- /// The 32-bit unsigned integer read.
- ///
public uint[] ReadUInt32Array(int length)
{
uint[] data = new uint[length];
+
for (int i = 0; i < length; i++)
{
data[i] = ReadUInt32();
@@ -225,21 +201,15 @@ namespace Avalonia.Media.Fonts.Tables
{
byte[] data = new byte[length];
- ReadInternal(data, length);
+ ReadBytesInternal(data, length);
return data;
}
- ///
- /// Reads array of 16-bit unsigned integers from the stream.
- ///
- /// The length.
- ///
- /// The 16-bit signed integer read.
- ///
public short[] ReadInt16Array(int length)
{
short[] data = new short[length];
+
for (int i = 0; i < length; i++)
{
data[i] = ReadInt16();
@@ -248,10 +218,6 @@ namespace Avalonia.Media.Fonts.Tables
return data;
}
- ///
- /// Reads an array of 16-bit signed integers from the stream to the buffer.
- ///
- /// The buffer to read to.
public void ReadInt16Array(Span buffer)
{
for (int i = 0; i < buffer.Length; i++)
@@ -260,110 +226,66 @@ namespace Avalonia.Media.Fonts.Tables
}
}
- ///
- /// Reads a 8-bit unsigned integer from the stream, using the bit converter
- /// for this reader. 1 bytes are read.
- ///
- /// The 8-bit unsigned integer read.
public byte ReadUInt8()
{
- ReadInternal(_buffer, 1);
- return _buffer[0];
+ EnsureAvailable(1);
+
+ return _span[_position++];
}
- ///
- /// Reads a 24-bit unsigned integer from the stream, using the bit converter
- /// for this reader. 3 bytes are read.
- ///
- /// The 24-bit unsigned integer read.
public int ReadUInt24()
{
byte highByte = ReadByte();
+
return (highByte << 16) | ReadUInt16();
}
- ///
- /// Reads a 32-bit unsigned integer from the stream, using the bit converter
- /// for this reader. 4 bytes are read.
- ///
- /// The 32-bit unsigned integer read.
public uint ReadUInt32()
{
- ReadInternal(_buffer, 4);
+ EnsureAvailable(4);
+
+ uint value = BinaryPrimitives.ReadUInt32BigEndian(_span.Slice(_position, 4));
- return BinaryPrimitives.ReadUInt32BigEndian(_buffer);
+ _position += 4;
+
+ return value;
}
- ///
- /// Reads a 32-bit unsigned integer from the stream representing an offset position.
- /// 4 bytes are read.
- ///
- /// The 32-bit unsigned integer read.
public uint ReadOffset32() => ReadUInt32();
- ///
- /// Reads the specified number of bytes, returning them in a new byte array.
- /// If not enough bytes are available before the end of the stream, this
- /// method will return what is available.
- ///
- /// The number of bytes to read.
- /// The bytes read.
public byte[] ReadBytes(int count)
{
- byte[] ret = new byte[count];
- int index = 0;
- while (index < count)
- {
- int read = BaseStream.Read(ret, index, count - index);
+ int available = Math.Min(count, _span.Length - _position);
- // Stream has finished half way through. That's fine, return what we've got.
- if (read == 0)
- {
- byte[] copy = new byte[index];
- Buffer.BlockCopy(ret, 0, copy, 0, index);
- return copy;
- }
+ byte[] ret = new byte[available];
- index += read;
- }
+ ReadBytesInternal(ret, available);
return ret;
}
- ///
- /// Reads a string of a specific length, which specifies the number of bytes
- /// to read from the stream. These bytes are then converted into a string with
- /// the encoding for this reader.
- ///
- /// The bytes to read.
- /// The encoding.
- ///
- /// The string read from the stream.
- ///
public string ReadString(int bytesToRead, Encoding encoding)
{
- byte[] data = new byte[bytesToRead];
- ReadInternal(data, bytesToRead);
- return encoding.GetString(data, 0, data.Length);
+ EnsureAvailable(bytesToRead);
+
+ string result = encoding.GetString(_span.Slice(_position, bytesToRead));
+
+ _position += bytesToRead;
+
+ return result;
}
- ///
- /// Reads the uint32 string.
- ///
- /// a 4 character long UTF8 encoded string.
public string ReadTag()
{
- ReadInternal(_buffer, 4);
+ EnsureAvailable(4);
- return Encoding.UTF8.GetString(_buffer, 0, 4);
+ string tag = Encoding.UTF8.GetString(_span.Slice(_position, 4));
+
+ _position += 4;
+
+ return tag;
}
- ///
- /// Reads an offset consuming the given nuber of bytes.
- ///
- /// The offset size in bytes.
- /// The 32-bit signed integer representing the offset.
- /// Size is not in range.
public int ReadOffset(int size)
=> size switch
{
@@ -374,33 +296,20 @@ namespace Avalonia.Media.Fonts.Tables
_ => throw new InvalidOperationException(),
};
- ///
- /// Reads the given number of bytes from the stream, throwing an exception
- /// if they can't all be read.
- ///
- /// Buffer to read into.
- /// Number of bytes to read.
- private void ReadInternal(byte[] data, int size)
+ private void ReadBytesInternal(byte[] data, int size)
{
- int index = 0;
+ EnsureAvailable(size);
- while (index < size)
- {
- int read = BaseStream.Read(data, index, size - index);
- if (read == 0)
- {
- throw new EndOfStreamException($"End of stream reached with {size - index} byte{(size - index == 1 ? "s" : string.Empty)} left to read.");
- }
+ _span.Slice(_position, size).CopyTo(data);
- index += read;
- }
+ _position += size;
}
- public void Dispose()
+ private readonly void EnsureAvailable(int size)
{
- if (!_leaveOpen)
+ if (_position + size > _span.Length)
{
- BaseStream?.Dispose();
+ throw new InvalidOperationException($"End of span reached with {size - (_span.Length - _position)} byte{(size - (_span.Length - _position) == 1 ? "s" : string.Empty)} left to read.");
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs
new file mode 100644
index 0000000000..9a461afb0b
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ ///
+ /// Provides a read-only mapping from Unicode code points to glyph identifiers for a font's character map (cmap)
+ /// table.
+ ///
+ /// This struct enables efficient lookup of glyph IDs corresponding to Unicode code points,
+ /// supporting both Format 4 (BMP) and Format 12 (Unicode full repertoire) cmap subtables.
+ ///
+#pragma warning disable CA1815 // Override equals not needed for readonly struct
+ public readonly struct CharacterToGlyphMap
+#pragma warning restore CA1815 // Override equals not needed for readonly struct
+ {
+ private readonly CmapFormat _format;
+ private readonly CmapFormat4Table? _format4;
+ private readonly CmapFormat12Table? _format12;
+
+ ///
+ /// Initializes a new instance of the CharacterToGlyphMap class using the specified Format 4 cmap table.
+ ///
+ /// The Format 4 cmap table that provides character-to-glyph mapping data. Cannot be null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal CharacterToGlyphMap(CmapFormat4Table table)
+ {
+ _format = CmapFormat.Format4;
+ _format4 = table;
+ _format12 = null;
+ }
+
+ ///
+ /// Initializes a new instance of the CharacterToGlyphMap class using the specified Format 12 character-to-glyph
+ /// mapping table.
+ ///
+ /// The Format 12 cmap table that defines the mapping from Unicode code points to glyph indices. Cannot be null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal CharacterToGlyphMap(CmapFormat12Table table)
+ {
+ _format = CmapFormat.Format12;
+ _format12 = table;
+ _format4 = null;
+ }
+
+ ///
+ /// Gets the glyph index associated with the specified Unicode code point.
+ ///
+ /// The Unicode code point for which to retrieve the glyph index.
+ /// The glyph index corresponding to the specified code point.
+ public ushort this[int codePoint]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => GetGlyph(codePoint);
+ }
+
+ ///
+ /// Retrieves the glyph index that corresponds to the specified Unicode code point.
+ ///
+ /// The Unicode code point for which to obtain the glyph index.
+ /// The glyph index associated with the specified code point. Returns 0 if the code point is not mapped to any
+ /// glyph.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ushort GetGlyph(int codePoint)
+ {
+ return _format switch
+ {
+ CmapFormat.Format4 => _format4!.GetGlyph(codePoint),
+ CmapFormat.Format12 => _format12!.GetGlyph(codePoint),
+ _ => 0
+ };
+ }
+
+ ///
+ /// Determines whether the character map contains a glyph for the specified Unicode code point.
+ ///
+ /// The Unicode code point to check for the presence of a corresponding glyph.
+ /// true if a glyph exists for the specified code point; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool ContainsGlyph(int codePoint)
+ {
+ return _format switch
+ {
+ CmapFormat.Format4 => _format4!.ContainsGlyph(codePoint),
+ CmapFormat.Format12 => _format12!.ContainsGlyph(codePoint),
+ _ => false
+ };
+ }
+
+ ///
+ /// Maps a sequence of Unicode code points to their corresponding glyph IDs using the current character mapping
+ /// format.
+ ///
+ /// If the current character mapping format is not supported, all entries in are set to zero. The mapping is performed in place, and the method does not allocate
+ /// additional memory.
+ /// A read-only span of Unicode code points to be mapped to glyph IDs.
+ /// A span in which the resulting glyph IDs are written. Must be at least as long as .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void GetGlyphs(ReadOnlySpan codePoints, Span glyphIds)
+ {
+ switch (_format)
+ {
+ case CmapFormat.Format4:
+ _format4!.GetGlyphs(codePoints, glyphIds);
+ return;
+ case CmapFormat.Format12:
+ _format12!.GetGlyphs(codePoints, glyphIds);
+ return;
+ default:
+ glyphIds.Clear();
+ return;
+ }
+ }
+
+
+ ///
+ /// Attempts to retrieve the glyph identifier corresponding to the specified Unicode code point.
+ ///
+ /// The Unicode code point for which to obtain the glyph identifier.
+ /// When this method returns, contains the glyph identifier associated with the specified code point, if found;
+ /// otherwise, zero. This parameter is passed uninitialized.
+ /// true if a glyph identifier was found for the specified code point; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetGlyph(int codePoint, out ushort glyphId)
+ {
+ switch (_format)
+ {
+ case CmapFormat.Format4: return _format4!.TryGetGlyph(codePoint, out glyphId);
+ case CmapFormat.Format12: return _format12!.TryGetGlyph(codePoint, out glyphId);
+ default: glyphId = 0; return false;
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through all code point ranges mapped by this instance.
+ ///
+ /// A that can be used to enumerate the mapped code point ranges.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CodepointRangeEnumerator GetMappedRanges()
+ {
+ return new CodepointRangeEnumerator(_format, _format4, _format12);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapEncoding.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapEncoding.cs
new file mode 100644
index 0000000000..8979e21d7a
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapEncoding.cs
@@ -0,0 +1,34 @@
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ // Encoding IDs. The meaning depends on the platform; common values are listed here.
+ internal enum CmapEncoding : ushort
+ {
+ // Unicode platform encodings
+ Unicode_1_0 = 0,
+ Unicode_1_1 = 1,
+ Unicode_ISO_10646 = 2,
+ Unicode_2_0_BMP = 3,
+ Unicode_2_0_full = 4,
+
+ // Macintosh encodings (selected)
+ Macintosh_Roman = 0,
+ Macintosh_Japanese = 1,
+ Macintosh_ChineseTraditional = 2,
+ Macintosh_Korean = 3,
+ Macintosh_Arabic = 4,
+ Macintosh_Hebrew = 5,
+ Macintosh_Greek = 6,
+ Macintosh_Russian = 7,
+ Macintosh_RSymbol = 8,
+
+ // Microsoft encodings
+ Microsoft_Symbol = 0,
+ Microsoft_UnicodeBMP = 1, // UCS-2 / UTF-16 (BMP)
+ Microsoft_ShiftJIS = 2,
+ Microsoft_PRChina = 3,
+ Microsoft_Big5 = 4,
+ Microsoft_Wansung = 5,
+ Microsoft_Johab = 6,
+ Microsoft_UCS4 = 10 // UTF-32 (format 12)
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat.cs
new file mode 100644
index 0000000000..667498bd43
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat.cs
@@ -0,0 +1,16 @@
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ // cmap format types
+ internal enum CmapFormat : ushort
+ {
+ Format0 = 0, // Byte encoding table
+ Format2 = 2, // High-byte mapping through table (multi-byte charsets)
+ Format4 = 4, // Segment mapping to delta values (most common)
+ Format6 = 6, // Trimmed table mapping
+ Format8 = 8, // Mixed 16/32-bit coverage
+ Format10 = 10, // Trimmed array mapping (32-bit)
+ Format12 = 12, // Segmented coverage (32-bit)
+ Format13 = 13, // Many-to-one mappings
+ Format14 = 14, // Unicode Variation Sequences
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Table.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Table.cs
new file mode 100644
index 0000000000..b4440e7884
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Table.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Buffers.Binary;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ internal sealed class CmapFormat12Table
+ {
+ private readonly ReadOnlyMemory _table;
+ private readonly int _groupCount;
+ private readonly ReadOnlyMemory _groups;
+
+ ///
+ /// Gets the language code for the cmap subtable.
+ /// For non-language-specific tables, this value is 0.
+ ///
+ public uint Language { get; }
+
+ public CmapFormat12Table(ReadOnlyMemory table)
+ {
+ var reader = new BigEndianBinaryReader(table.Span);
+
+ ushort format = reader.ReadUInt16();
+ Debug.Assert(format == 12, "Format must be 12.");
+
+ ushort reserved = reader.ReadUInt16();
+ Debug.Assert(reserved == 0, "Reserved field must be 0.");
+
+ uint length = reader.ReadUInt32();
+
+ _table = table.Slice(0, (int)length);
+
+ Language = reader.ReadUInt32();
+
+ _groupCount = (int)reader.ReadUInt32();
+
+ int groupsOffset = reader.Position;
+ int groupsLength = _groupCount * 12;
+
+ Debug.Assert(length >= groupsOffset + groupsLength, "Length must cover all groups.");
+
+ _groups = _table.Slice(groupsOffset, groupsLength);
+ }
+
+ ///
+ /// Retrieves the glyph index corresponding to the specified Unicode code point.
+ ///
+ /// The Unicode code point for which to obtain the glyph index. Must be a valid code point supported by the
+ /// font.
+ /// The glyph index as an unsigned 16-bit integer for the specified code point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ushort GetGlyph(int codePoint) => this[codePoint];
+
+ ///
+ /// Determines whether the specified Unicode code point is present in the glyph set.
+ ///
+ /// The Unicode code point to check for presence in the glyph set. Must be a valid integer representing a
+ /// Unicode character.
+ /// true if the glyph set contains the specified code point; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool ContainsGlyph(int codePoint)
+ {
+ return FindGroupIndex(codePoint) >= 0;
+ }
+
+ ///
+ /// Maps multiple Unicode code points to glyph indices in a single operation.
+ ///
+ /// Read-only span of code points to map.
+ /// Output span to write glyph IDs. Must be at least as long as .
+ ///
+ /// This method is significantly more efficient than calling the indexer multiple times as it:
+ /// - Reuses span references (no repeated memory access)
+ /// - Caches group data for sequential lookups
+ /// - Optimizes for locality of code points (common in text runs)
+ /// Format 12 is commonly used for fonts with large character sets (CJK, emoji, etc.)
+ /// This is the preferred method for batch character-to-glyph mapping in text shaping.
+ ///
+ public void GetGlyphs(ReadOnlySpan codePoints, Span glyphIds)
+ {
+ if (glyphIds.Length < codePoints.Length)
+ {
+ throw new ArgumentException("Output span must be at least as long as input span", nameof(glyphIds));
+ }
+
+ var groups = _groups.Span;
+
+ // Track last group for locality optimization
+ int lastGroup = -1;
+ uint lastStart = 0;
+ uint lastEnd = 0;
+ uint lastStartGlyph = 0;
+
+ for (int i = 0; i < codePoints.Length; i++)
+ {
+ int codePoint = codePoints[i];
+
+ // Optimization: check if codepoint is in the same group as previous
+ if (lastGroup >= 0 && codePoint >= lastStart && codePoint <= lastEnd)
+ {
+ glyphIds[i] = (ushort)(lastStartGlyph + (codePoint - lastStart));
+ continue;
+ }
+
+ // Binary search for group
+ int groupIndex = FindGroupIndexOptimized(codePoint, groups);
+
+ if (groupIndex < 0)
+ {
+ glyphIds[i] = 0;
+ lastGroup = -1;
+
+ continue;
+ }
+
+ // Cache group data
+ lastGroup = groupIndex;
+ lastStart = ReadUInt32BE(groups, groupIndex, 0);
+ lastEnd = ReadUInt32BE(groups, groupIndex, 4);
+ lastStartGlyph = ReadUInt32BE(groups, groupIndex, 8);
+
+ glyphIds[i] = (ushort)(lastStartGlyph + (codePoint - lastStart));
+ }
+ }
+
+ public bool TryGetGlyph(int codePoint, out ushort glyphId)
+ {
+ int groupIndex = FindGroupIndex(codePoint);
+
+ if (groupIndex < 0)
+ {
+ glyphId = 0;
+ return false;
+ }
+
+ var groups = _groups.Span;
+
+ uint start = ReadUInt32BE(groups, groupIndex, 0);
+ uint startGlyph = ReadUInt32BE(groups, groupIndex, 8);
+
+ glyphId = (ushort)(startGlyph + (codePoint - start));
+
+ return glyphId != 0;
+ }
+
+ internal bool TryGetRange(int index, out CodepointRange range)
+ {
+ if ((uint)index >= (uint)_groupCount)
+ {
+ range = default;
+
+ return false;
+ }
+
+ var groups = _groups.Span;
+
+ int start = (int)ReadUInt32BE(groups, index, 0);
+ int end = (int)ReadUInt32BE(groups, index, 4);
+
+ range = new CodepointRange(start, end);
+
+ return true;
+ }
+
+ public ushort this[int codePoint]
+ {
+ get
+ {
+ int groupIndex = FindGroupIndex(codePoint);
+
+ if (groupIndex < 0)
+ {
+ return 0;
+ }
+
+ var groups = _groups.Span;
+
+ uint start = ReadUInt32BE(groups, groupIndex, 0);
+ uint startGlyph = ReadUInt32BE(groups, groupIndex, 8);
+
+ // Calculate glyph index
+ return (ushort)(startGlyph + (codePoint - start));
+ }
+ }
+
+ // Optimized binary search that works directly with cached span
+ private int FindGroupIndexOptimized(int codePoint, ReadOnlySpan groups)
+ {
+ int lo = 0;
+ int hi = _groupCount - 1;
+
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) >> 1;
+ uint start = ReadUInt32BE(groups, mid, 0);
+ uint end = ReadUInt32BE(groups, mid, 4);
+
+ if (codePoint < start)
+ {
+ hi = mid - 1;
+ }
+ else if (codePoint > end)
+ {
+ lo = mid + 1;
+ }
+ else
+ {
+ return mid;
+ }
+ }
+
+ return -1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint ReadUInt32BE(ReadOnlySpan span, int groupIndex, int fieldOffset)
+ {
+ int byteIndex = groupIndex * 12 + fieldOffset;
+
+ return BinaryPrimitives.ReadUInt32BigEndian(span.Slice(byteIndex, 4));
+ }
+
+ // Binary search to find the group containing the code point
+ private int FindGroupIndex(int codePoint)
+ {
+ int lo = 0;
+ int hi = _groupCount - 1;
+
+ var groups = _groups.Span;
+
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) >> 1;
+ uint start = ReadUInt32BE(groups, mid, 0);
+ uint end = ReadUInt32BE(groups, mid, 4);
+
+ if (codePoint < start)
+ {
+ hi = mid - 1;
+ }
+ else if (codePoint > end)
+ {
+ lo = mid + 1;
+ }
+ else
+ {
+ return mid;
+ }
+ }
+
+ // Not found
+ return -1;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat4Table.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat4Table.cs
new file mode 100644
index 0000000000..7d1fded616
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat4Table.cs
@@ -0,0 +1,454 @@
+using System;
+using System.Buffers.Binary;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ internal sealed class CmapFormat4Table
+ {
+ private readonly ReadOnlyMemory _table;
+
+ private readonly int _segCount;
+ private readonly ReadOnlyMemory _endCodes;
+ private readonly ReadOnlyMemory _startCodes;
+ private readonly ReadOnlyMemory _idDeltas;
+ private readonly ReadOnlyMemory _idRangeOffsets;
+ private readonly ReadOnlyMemory _glyphIdArray;
+
+ ///
+ /// Gets the language code for the cmap subtable.
+ /// For non-language-specific tables, this value is 0.
+ ///
+ public ushort Language { get; }
+
+ public CmapFormat4Table(ReadOnlyMemory table)
+ {
+ var reader = new BigEndianBinaryReader(table.Span);
+
+ ushort format = reader.ReadUInt16(); // must be 4
+
+ Debug.Assert(format == 4, "Format must be 4.");
+
+ ushort length = reader.ReadUInt16(); // length in bytes of this subtable
+
+ _table = table.Slice(0, length);
+
+ Language = reader.ReadUInt16(); // language code, 0 for non-language-specific
+
+ ushort segCountX2 = reader.ReadUInt16(); // 2 * segCount
+ _segCount = segCountX2 / 2;
+
+ ushort searchRange = reader.ReadUInt16(); // searchRange = 2 * (2^floor(log2(segCount)))
+ ushort entrySelector = reader.ReadUInt16(); // entrySelector = log2(searchRange/2)
+ ushort rangeShift = reader.ReadUInt16(); // rangeShift = segCountX2 - searchRange
+
+ // Spec sanity checks (warn in debug builds instead of asserting)
+#if DEBUG
+ var expectedSearchRange = (ushort)(2 * (1 << (int)Math.Floor(Math.Log(_segCount, 2))));
+ if (searchRange != expectedSearchRange)
+ {
+ Debug.WriteLine($"CMAP format 4: unexpected searchRange {searchRange}, expected {expectedSearchRange} for segCount {_segCount}.");
+ }
+
+ var expectedEntrySelector = (ushort)Math.Floor(Math.Log(_segCount, 2));
+ if (entrySelector != expectedEntrySelector)
+ {
+ Debug.WriteLine($"CMAP format 4: unexpected entrySelector {entrySelector}, expected {expectedEntrySelector} for segCount {_segCount}.");
+ }
+
+ var expectedRangeShift = (ushort)(segCountX2 - searchRange);
+ if (rangeShift != expectedRangeShift)
+ {
+ Debug.WriteLine($"CMAP format 4: unexpected rangeShift {rangeShift}, expected {expectedRangeShift} for segCountX2 {segCountX2} and searchRange {searchRange}.");
+ }
+#endif
+
+ // Compute offsets
+ int endCodeOffset = reader.Position;
+ int startCodeOffset = endCodeOffset + _segCount * 2 + 2; // + reservedPad
+ int idDeltaOffset = startCodeOffset + _segCount * 2; // after startCodes
+ int idRangeOffsetOffset = idDeltaOffset + _segCount * 2; // after idDeltas
+ int glyphIdArrayOffset = idRangeOffsetOffset + _segCount * 2; // after idRangeOffsets
+
+ // Ensure declared length is consistent
+ Debug.Assert(length >= glyphIdArrayOffset,
+ "Subtable length must be at least large enough to contain glyphIdArray.");
+
+ // Slice directly
+ _endCodes = _table.Slice(endCodeOffset, _segCount * 2);
+
+ _startCodes = _table.Slice(startCodeOffset, _segCount * 2);
+
+ _idDeltas = _table.Slice(idDeltaOffset, _segCount * 2);
+
+ _idRangeOffsets = _table.Slice(idRangeOffsetOffset, _segCount * 2);
+
+ int glyphCount = (length - glyphIdArrayOffset) / 2;
+
+ Debug.Assert(glyphCount >= 0, "GlyphIdArray length must not be negative.");
+
+ _glyphIdArray = _table.Slice(glyphIdArrayOffset, glyphCount * 2);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ushort GetGlyph(int codePoint) => this[codePoint];
+
+ public bool ContainsGlyph(int codePoint)
+ {
+ int seg = FindSegmentIndex(codePoint);
+
+ if ((uint)seg >= (uint)_segCount)
+ {
+ return false;
+ }
+
+ ushort idRangeOffset = ReadUInt16BE(_idRangeOffsets.Span, seg);
+ ushort idDelta = ReadUInt16BE(_idDeltas.Span, seg);
+
+ if (idRangeOffset == 0)
+ {
+ // Always maps to something (possibly .notdef via delta)
+ return ((codePoint + idDelta) & 0xFFFF) != 0;
+ }
+
+ int start = ReadUInt16BE(_startCodes.Span, seg);
+ int ro = idRangeOffset >> 1;
+ int idx = (codePoint - start) + ro - (_segCount - seg);
+
+ if ((uint)idx >= (uint)(_glyphIdArray.Length >> 1))
+ {
+ return false;
+ }
+
+ ushort glyphId = ReadUInt16BE(_glyphIdArray.Span, idx);
+
+ return glyphId != 0;
+ }
+
+ ///
+ /// Maps multiple Unicode code points to glyph indices in a single operation.
+ ///
+ /// Read-only span of code points to map.
+ /// Output span to write glyph IDs. Must be at least as long as .
+ ///
+ /// This method is significantly more efficient than calling the indexer multiple times as it:
+ /// - Reuses span references (no repeated .Span property access)
+ /// - Caches segment data for sequential lookups
+ /// - Optimizes for locality of code points (common in text runs)
+ /// This is the preferred method for batch character-to-glyph mapping in text shaping.
+ ///
+ public void GetGlyphs(ReadOnlySpan codePoints, Span glyphIds)
+ {
+ if (glyphIds.Length < codePoints.Length)
+ {
+ throw new ArgumentException("Output span must be at least as long as input span", nameof(glyphIds));
+ }
+
+ // Cache all spans once
+ var startCodes = _startCodes.Span;
+ var endCodes = _endCodes.Span;
+ var idDeltas = _idDeltas.Span;
+ var idRangeOffsets = _idRangeOffsets.Span;
+ var glyphIdArray = _glyphIdArray.Span;
+ int glyphArrayWords = glyphIdArray.Length / 2;
+
+ // Track last segment for locality optimization
+ int lastSegment = -1;
+
+ for (int i = 0; i < codePoints.Length; i++)
+ {
+ int codePoint = codePoints[i];
+ int segmentIndex;
+
+ // Optimization: check if codepoint is in the same segment as previous
+ if (lastSegment >= 0 && lastSegment < _segCount)
+ {
+ int lastStart = ReadUInt16BE(startCodes, lastSegment);
+ int lastEnd = ReadUInt16BE(endCodes, lastSegment);
+
+ if (codePoint >= lastStart && codePoint <= lastEnd)
+ {
+ segmentIndex = lastSegment;
+ goto MapGlyph;
+ }
+ }
+
+ // Binary search for segment
+ segmentIndex = FindSegmentIndexOptimized(codePoint, startCodes, endCodes);
+
+ if (segmentIndex < 0)
+ {
+ glyphIds[i] = 0;
+ continue;
+ }
+
+ lastSegment = segmentIndex;
+
+ MapGlyph:
+ ushort idRangeOffset = ReadUInt16BE(idRangeOffsets, segmentIndex);
+ ushort idDelta = ReadUInt16BE(idDeltas, segmentIndex);
+
+ if (idRangeOffset == 0)
+ {
+ glyphIds[i] = (ushort)((codePoint + idDelta) & 0xFFFF);
+ }
+ else
+ {
+ int start = ReadUInt16BE(startCodes, segmentIndex);
+ int ro = idRangeOffset / 2;
+ int idx = (codePoint - start) + ro - (_segCount - segmentIndex);
+
+ if ((uint)idx < (uint)glyphArrayWords)
+ {
+ ushort glyphId = ReadUInt16BE(glyphIdArray, idx);
+
+ if (glyphId != 0)
+ {
+ glyphId = (ushort)((glyphId + idDelta) & 0xFFFF);
+ }
+
+ glyphIds[i] = glyphId;
+ }
+ else
+ {
+ glyphIds[i] = 0;
+ }
+ }
+ }
+ }
+
+ public bool TryGetGlyph(int codePoint, out ushort glyphId)
+ {
+ int seg = FindSegmentIndex(codePoint);
+
+ if ((uint)seg >= (uint)_segCount)
+ {
+ glyphId = 0;
+
+ return false;
+ }
+
+ ushort idRangeOffset = ReadUInt16BE(_idRangeOffsets.Span, seg);
+ ushort idDelta = ReadUInt16BE(_idDeltas.Span, seg);
+
+ if (idRangeOffset == 0)
+ {
+ glyphId = (ushort)((codePoint + idDelta) & 0xFFFF);
+
+ return glyphId != 0;
+ }
+
+ int start = ReadUInt16BE(_startCodes.Span, seg);
+ int ro = idRangeOffset >> 1;
+ int idx = (codePoint - start) + ro - (_segCount - seg);
+
+ if ((uint)idx >= (uint)(_glyphIdArray.Length >> 1))
+ {
+ glyphId = 0;
+
+ return false;
+ }
+
+ glyphId = ReadUInt16BE(_glyphIdArray.Span, idx);
+
+ if (glyphId != 0)
+ {
+ glyphId = (ushort)((glyphId + idDelta) & 0xFFFF);
+ }
+
+ return glyphId != 0;
+ }
+
+ internal bool TryGetRange(int index, out CodepointRange range)
+ {
+ if ((uint)index >= (uint)_segCount)
+ {
+ range = default;
+ return false;
+ }
+
+ int start = ReadUInt16BE(_startCodes.Span, index);
+ int end = ReadUInt16BE(_endCodes.Span, index);
+
+ // Skip sentinel segment (0xFFFF)
+ if (start == 0xFFFF && end == 0xFFFF)
+ {
+ range = default;
+
+ return false;
+ }
+
+ range = new CodepointRange(start, end);
+
+ return true;
+ }
+
+ public ushort this[int codePoint]
+ {
+ get
+ {
+ // Find the segment containing the codePoint
+ int segmentIndex = FindSegmentIndex(codePoint);
+
+ if (segmentIndex < 0)
+ {
+ return 0;
+ }
+
+ ushort idRangeOffset = ReadUInt16BE(_idRangeOffsets.Span, segmentIndex);
+ ushort idDelta = ReadUInt16BE(_idDeltas.Span, segmentIndex);
+
+ // If idRangeOffset is 0, glyphId = (codePoint + idDelta) % 65536
+ if (idRangeOffset == 0)
+ {
+ return (ushort)((codePoint + idDelta) & 0xFFFF);
+ }
+ else
+ {
+ int start = ReadUInt16BE(_startCodes.Span, segmentIndex);
+ int ro = idRangeOffset / 2; // words
+ // The index into the glyphIdArray
+ int idx = (codePoint - start) + ro - (_segCount - segmentIndex);
+
+ // Ensure index is within bounds of glyphIdArray
+ int glyphArrayWords = _glyphIdArray.Length / 2;
+
+ if ((uint)idx < (uint)glyphArrayWords)
+ {
+ ushort glyphId = ReadUInt16BE(_glyphIdArray.Span, idx);
+
+ // If glyphId is not 0, apply idDelta
+ if (glyphId != 0)
+ {
+ glyphId = (ushort)((glyphId + idDelta) & 0xFFFF);
+ }
+
+ return glyphId;
+ }
+ }
+
+ // Not found or maps to missing glyph
+ return 0;
+ }
+ }
+
+ // Resolves the glyph ID for a given code point within a specific segment
+ private ushort ResolveGlyph(int segmentIndex, int codePoint)
+ {
+ ushort idRangeOffset = ReadUInt16BE(_idRangeOffsets.Span, segmentIndex);
+ ushort idDelta = ReadUInt16BE(_idDeltas.Span, segmentIndex);
+
+ if (idRangeOffset == 0)
+ {
+ return (ushort)((codePoint + idDelta) & 0xFFFF);
+ }
+ else
+ {
+ int start = ReadUInt16BE(_startCodes.Span, segmentIndex);
+ int ro = idRangeOffset / 2; // words
+ int idx = (codePoint - start) + ro - (_segCount - segmentIndex);
+ int glyphArrayWords = _glyphIdArray.Length / 2;
+
+ if ((uint)idx < (uint)glyphArrayWords)
+ {
+ ushort glyphId = ReadUInt16BE(_glyphIdArray.Span, idx);
+
+ if (glyphId != 0)
+ {
+ glyphId = (ushort)((glyphId + idDelta) & 0xFFFF);
+ }
+
+ return glyphId;
+ }
+ }
+
+ // Not found or maps to missing glyph
+ return 0;
+ }
+
+ private int FindSegmentIndex(int codePoint)
+ {
+ int lo = 0;
+ int hi = _segCount - 1;
+
+ var startCodes = _startCodes.Span;
+ var endCodes = _endCodes.Span;
+
+ // Binary search over endCodes (sorted ascending)
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) >> 1;
+ int end = ReadUInt16BE(endCodes, mid);
+
+ if (codePoint > end)
+ {
+ lo = mid + 1;
+ }
+ else
+ {
+ hi = mid - 1;
+ }
+ }
+
+ // lo is now the first segment whose endCode >= codePoint
+ if (lo < _segCount)
+ {
+ int start = ReadUInt16BE(startCodes, lo);
+
+ if (codePoint >= start)
+ {
+ return lo;
+ }
+ }
+
+ return -1; // not found
+ }
+
+ // Optimized binary search that works directly with cached spans
+ private int FindSegmentIndexOptimized(int codePoint, ReadOnlySpan startCodes, ReadOnlySpan endCodes)
+ {
+ int lo = 0;
+ int hi = _segCount - 1;
+
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) >> 1;
+ int end = ReadUInt16BE(endCodes, mid);
+
+ if (codePoint > end)
+ {
+ lo = mid + 1;
+ }
+ else
+ {
+ hi = mid - 1;
+ }
+ }
+
+ if (lo < _segCount)
+ {
+ int start = ReadUInt16BE(startCodes, lo);
+
+ if (codePoint >= start)
+ {
+ return lo;
+ }
+ }
+
+ return -1;
+ }
+
+ // Reads a big-endian UInt16 from the specified word index in the given memory
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ushort ReadUInt16BE(ReadOnlySpan span, int wordIndex)
+ {
+ int byteIndex = wordIndex * 2;
+
+ // Ensure we don't go out of bounds
+ return BinaryPrimitives.ReadUInt16BigEndian(span.Slice(byteIndex, 2));
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapSubtableEntry.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapSubtableEntry.cs
new file mode 100644
index 0000000000..a05470a7c9
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapSubtableEntry.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ // Representation of a subtable entry in the 'cmap' table directory
+ internal readonly record struct CmapSubtableEntry
+ {
+ public CmapSubtableEntry(PlatformID platform, CmapEncoding encoding, int offset, CmapFormat format)
+ {
+ Platform = platform;
+ Encoding = encoding;
+ Offset = offset;
+ Format = format;
+ }
+
+ ///
+ /// Gets the platform identifier for the current environment.
+ ///
+ public PlatformID Platform { get; }
+
+ ///
+ /// Gets the character map (CMap) encoding associated with this instance.
+ ///
+ ///
+ public CmapEncoding Encoding { get; }
+
+ ///
+ /// Gets the offset of the sub table.
+ ///
+ public int Offset { get; }
+
+ ///
+ /// Gets the format of the character-to-glyph mapping (cmap) table.
+ ///
+ public CmapFormat Format { get; }
+
+ public ReadOnlyMemory GetSubtableMemory(ReadOnlyMemory table)
+ {
+ return table.Slice(Offset);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs
new file mode 100644
index 0000000000..f526658133
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ ///
+ /// Represents the 'cmap' table in an OpenType font, which maps character codes to glyph indices.
+ ///
+ /// The 'cmap' table is a critical component of an OpenType font, enabling the mapping of
+ /// character codes (e.g., Unicode) to glyph indices used for rendering text. This class provides functionality to
+ /// load and parse the 'cmap' table from a font's platform-specific typeface.
+ internal sealed class CmapTable
+ {
+ internal const string TableName = "cmap";
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
+
+ public static CharacterToGlyphMap Load(GlyphTypeface glyphTypeface)
+ {
+ if (!glyphTypeface.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ throw new InvalidOperationException("No cmap table found.");
+ }
+
+ var reader = new BigEndianBinaryReader(table.Span);
+
+ reader.ReadUInt16(); // version
+
+ var numTables = reader.ReadUInt16();
+
+ var entries = new CmapSubtableEntry[numTables];
+
+ for (var i = 0; i < numTables; i++)
+ {
+ var platformID = (PlatformID)reader.ReadUInt16();
+ var encodingID = (CmapEncoding)reader.ReadUInt16();
+ var offset = (int)reader.ReadUInt32();
+
+ var position = reader.Position;
+
+ reader.Seek(offset);
+
+ var format = (CmapFormat)reader.ReadUInt16();
+
+ reader.Seek(position);
+
+ var entry = new CmapSubtableEntry(platformID, encodingID, offset, format);
+
+ entries[i] = entry;
+ }
+
+ // Try to find the best Format 12 subtable entry
+ if (TryFindFormat12Entry(entries, out var format12Entry))
+ {
+ // Prefer Format 12 if available
+ return new CharacterToGlyphMap(new CmapFormat12Table(format12Entry.GetSubtableMemory(table)));
+ }
+
+ // Fallback to Format 4
+ if (TryFindFormat4Entry(entries, out var format4Entry))
+ {
+ return new CharacterToGlyphMap(new CmapFormat4Table(format4Entry.GetSubtableMemory(table)));
+ }
+
+ throw new InvalidOperationException("No suitable cmap subtable found.");
+
+ // Tries to find the best Format 12 subtable entry based on platform and encoding preferences
+ static bool TryFindFormat12Entry(CmapSubtableEntry[] entries, out CmapSubtableEntry result)
+ {
+ result = default;
+ var foundPlatformScore = int.MaxValue;
+ var foundEncodingScore = int.MaxValue;
+
+ foreach (var entry in entries)
+ {
+ if (entry.Format != CmapFormat.Format12)
+ {
+ continue;
+ }
+
+ var platformScore = entry.Platform switch
+ {
+ PlatformID.Unicode => 0,
+ PlatformID.Windows => 1,
+ _ => 2
+ };
+
+ var encodingScore = 2; // Default: lowest preference
+
+ switch (entry.Platform)
+ {
+ case PlatformID.Unicode when entry.Encoding == CmapEncoding.Unicode_2_0_full:
+ encodingScore = 0; // non-BMP preferred
+ break;
+ case PlatformID.Unicode when entry.Encoding == CmapEncoding.Unicode_2_0_BMP:
+ encodingScore = 1; // BMP
+ break;
+ case PlatformID.Windows when entry.Encoding == CmapEncoding.Microsoft_UCS4 && platformScore != 0:
+ encodingScore = 0; // non-BMP preferred
+ break;
+ case PlatformID.Windows when entry.Encoding == CmapEncoding.Microsoft_UnicodeBMP && platformScore != 0:
+ encodingScore = 1; // BMP
+ break;
+ }
+
+ if (encodingScore < foundEncodingScore || encodingScore == foundEncodingScore && platformScore < foundPlatformScore)
+ {
+ result = entry;
+ foundEncodingScore = encodingScore;
+ foundPlatformScore = platformScore;
+ }
+ else
+ {
+ if (platformScore < foundPlatformScore)
+ {
+ result = entry;
+ foundEncodingScore = encodingScore;
+ foundPlatformScore = platformScore;
+ }
+ }
+
+ if (foundPlatformScore == 0 && foundEncodingScore == 0)
+ {
+ break; // Best possible match found
+ }
+ }
+
+ return result.Format != CmapFormat.Format0;
+ }
+
+ // Tries to find the best Format 4 subtable entry based on platform preferences
+ static bool TryFindFormat4Entry(CmapSubtableEntry[] entries, out CmapSubtableEntry result)
+ {
+ result = default;
+ var foundPlatformScore = int.MaxValue;
+
+ foreach (var entry in entries)
+ {
+ if (entry.Format != CmapFormat.Format4)
+ {
+ continue;
+ }
+
+ var platformScore = entry.Platform switch
+ {
+ PlatformID.Unicode => 0,
+ PlatformID.Windows => 1,
+ _ => 2
+ };
+
+ if (platformScore < foundPlatformScore)
+ {
+ result = entry;
+ foundPlatformScore = platformScore;
+ }
+
+ if (foundPlatformScore == 0)
+ {
+ break; // Best possible match found
+ }
+ }
+
+ return result.Format != CmapFormat.Format0;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRange.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRange.cs
new file mode 100644
index 0000000000..06381f1cfc
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRange.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ ///
+ /// Represents a range of Unicode code points, defined by inclusive start and end values.
+ ///
+ public readonly struct CodepointRange
+ {
+ ///
+ /// Gets the start of the range.
+ ///
+ public readonly int Start;
+
+ ///
+ /// Gets the end of the range.
+ ///
+ public readonly int End;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CodepointRange(int start, int end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is CodepointRange range &&
+ Start == range.Start &&
+ End == range.End;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Start, End);
+ }
+
+ public static bool operator ==(CodepointRange left, CodepointRange right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(CodepointRange left, CodepointRange right)
+ {
+ return !(left == right);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs
new file mode 100644
index 0000000000..b631c264d1
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs
@@ -0,0 +1,71 @@
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Media.Fonts.Tables.Cmap
+{
+ ///
+ /// Enumerates contiguous ranges of Unicode code points present in a character map (cmap) table.
+ ///
+ /// This enumerator is typically used to iterate over all code point ranges defined by a cmap
+ /// table in an OpenType or TrueType font. It supports both Format 4 and Format 12 cmap subtables. The enumerator is
+ /// a ref struct and must be used within the stack context; it cannot be stored on the heap or used across await or
+ /// yield boundaries.
+ public ref struct CodepointRangeEnumerator
+ {
+ private readonly CmapFormat _format;
+ private readonly CmapFormat4Table? _f4;
+ private readonly CmapFormat12Table? _f12;
+ private int _index;
+
+ internal CodepointRangeEnumerator(CmapFormat format, CmapFormat4Table? f4, CmapFormat12Table? f12)
+ {
+ _format = format;
+ _f4 = f4;
+ _f12 = f12;
+ _index = -1;
+ }
+
+ ///
+ /// Gets the current code point range in the enumeration sequence.
+ ///
+ public CodepointRange Current { get; private set; }
+
+ ///
+ /// Advances the enumerator to the next character mapping range in the collection.
+ ///
+ /// After calling MoveNext, check the Current property to access the current character
+ /// mapping range. If the end of the collection is reached, MoveNext returns false and Current is set to its
+ /// default value.
+ /// true if the enumerator was successfully advanced to the next range; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ _index++;
+
+ switch (_format)
+ {
+ case CmapFormat.Format4:
+ {
+ var result = _f4!.TryGetRange(_index, out var range);
+
+ Current = range;
+
+ return result;
+ }
+ case CmapFormat.Format12:
+ {
+ var result = _f12!.TryGetRange(_index, out var range);
+
+ Current = range;
+
+ return result;
+ }
+ default:
+ {
+ Current = default;
+
+ return false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/FeatureListTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/FeatureListTable.cs
index 0a916c7ed0..23cf0144b3 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/FeatureListTable.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/FeatureListTable.cs
@@ -2,8 +2,8 @@
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/blob/034a440aece357341fcc6b02db58ffbe153e54ef/src/SixLabors.Fonts
+using System;
using System.Collections.Generic;
-using System.IO;
namespace Avalonia.Media.Fonts.Tables
{
@@ -17,8 +17,8 @@ namespace Avalonia.Media.Fonts.Tables
///
internal class FeatureListTable
{
- private static OpenTypeTag GSubTag = OpenTypeTag.Parse("GSUB");
- private static OpenTypeTag GPosTag = OpenTypeTag.Parse("GPOS");
+ private static OpenTypeTag GSubTag { get; } = OpenTypeTag.Parse("GSUB");
+ private static OpenTypeTag GPosTag { get; } = OpenTypeTag.Parse("GPOS");
private FeatureListTable(IReadOnlyList features)
{
@@ -27,34 +27,31 @@ namespace Avalonia.Media.Fonts.Tables
public IReadOnlyList Features { get; }
- public static FeatureListTable? LoadGSub(IGlyphTypeface glyphTypeface)
+ public static FeatureListTable? LoadGSub(GlyphTypeface glyphTypeface)
{
- if (!glyphTypeface.TryGetTable(GSubTag, out var gPosTable))
+ if (!glyphTypeface.PlatformTypeface.TryGetTable(GSubTag, out var gPosTable))
{
return null;
}
- using var stream = new MemoryStream(gPosTable);
- using var reader = new BigEndianBinaryReader(stream, false);
-
- return Load(reader);
+ var reader = new BigEndianBinaryReader(gPosTable.Span);
+ return Load(ref reader);
}
- public static FeatureListTable? LoadGPos(IGlyphTypeface glyphTypeface)
+
+ public static FeatureListTable? LoadGPos(GlyphTypeface glyphTypeface)
{
- if (!glyphTypeface.TryGetTable(GPosTag, out var gSubTable))
+ if (!glyphTypeface.PlatformTypeface.TryGetTable(GPosTag, out var gSubTable))
{
return null;
}
- using var stream = new MemoryStream(gSubTable);
- using var reader = new BigEndianBinaryReader(stream, false);
-
- return Load(reader);
+ var reader = new BigEndianBinaryReader(gSubTable.Span);
+ return Load(ref reader);
}
- private static FeatureListTable Load(BigEndianBinaryReader reader)
+ private static FeatureListTable Load(ref BigEndianBinaryReader reader)
{
// GPOS/GSUB Header, Version 1.0
// +----------+-------------------+-----------------------------------------------------------+
@@ -73,14 +70,14 @@ namespace Avalonia.Media.Fonts.Tables
reader.ReadUInt16();
reader.ReadUInt16();
-
reader.ReadOffset16();
+
var featureListOffset = reader.ReadOffset16();
- return Load(reader, featureListOffset);
+ return Load(ref reader, featureListOffset);
}
- private static FeatureListTable Load(BigEndianBinaryReader reader, long offset)
+ private static FeatureListTable Load(ref BigEndianBinaryReader reader, int offset)
{
// FeatureList
// +---------------+------------------------------+-----------------------------------------------------------------------------------------------------------------+
@@ -90,11 +87,21 @@ namespace Avalonia.Media.Fonts.Tables
// +---------------+------------------------------+-----------------------------------------------------------------------------------------------------------------+
// | FeatureRecord | featureRecords[featureCount] | Array of FeatureRecords — zero-based (first feature has FeatureIndex = 0), listed alphabetically by feature tag |
// +---------------+------------------------------+-----------------------------------------------------------------------------------------------------------------+
- reader.Seek(offset, SeekOrigin.Begin);
+ reader.Seek(offset);
var featureCount = reader.ReadUInt16();
- var features = new List(featureCount);
+ if (featureCount == 0)
+ {
+ return new FeatureListTable(Array.Empty());
+ }
+
+ // Use stackalloc for small counts, array for larger
+ Span tempFeatures = featureCount <= 64
+ ? stackalloc OpenTypeTag[featureCount]
+ : new OpenTypeTag[featureCount];
+
+ int uniqueCount = 0;
for (var i = 0; i < featureCount; i++)
{
@@ -107,19 +114,24 @@ namespace Avalonia.Media.Fonts.Tables
// | Offset16 | featureOffset | Offset to Feature table, from beginning of FeatureList |
// +----------+---------------+--------------------------------------------------------+
var featureTag = reader.ReadUInt32();
-
reader.ReadOffset16();
var tag = new OpenTypeTag(featureTag);
- if (!features.Contains(tag))
+ // Check for duplicates in already added features
+ bool isDuplicate = tempFeatures.Contains(tag);
+
+ if (!isDuplicate)
{
- features.Add(tag);
+ tempFeatures[uniqueCount++] = tag;
}
}
- return new FeatureListTable(features /*featureTables*/);
- }
+ // Create array with only unique features
+ var features = new OpenTypeTag[uniqueCount];
+ tempFeatures.Slice(0, uniqueCount).CopyTo(features);
+ return new FeatureListTable(features);
+ }
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/FontVersion.cs b/src/Avalonia.Base/Media/Fonts/Tables/FontVersion.cs
new file mode 100644
index 0000000000..23cc3674ae
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/FontVersion.cs
@@ -0,0 +1,75 @@
+using System.Diagnostics;
+
+namespace Avalonia.Media.Fonts.Tables
+{
+ ///
+ /// Represents a Version16Dot16 value from OpenType font tables.
+ /// The high 16 bits represent the major version, and the low 16 bits represent the minor version as a fraction.
+ ///
+ [DebuggerDisplay("{ToString(),nq}")]
+ internal readonly struct FontVersion
+ {
+ ///
+ /// Gets the major version number.
+ ///
+ public ushort Major { get; }
+
+ ///
+ /// Gets the minor version number (as a fraction of 65536).
+ ///
+ public ushort Minor { get; }
+
+ ///
+ /// Initializes a new instance of the struct from raw Version16Dot16 value.
+ ///
+ /// The 32-bit Version16Dot16 value.
+ public FontVersion(uint value)
+ {
+ Major = (ushort)(value >> 16);
+ Minor = (ushort)(value & 0xFFFF);
+ }
+
+ ///
+ /// Initializes a new instance of the struct from major and minor components.
+ ///
+ /// The major version number.
+ /// The minor version number (as a fraction of 65536).
+ public FontVersion(ushort major, ushort minor)
+ {
+ Major = major;
+ Minor = minor;
+ }
+
+ ///
+ /// Converts the version to a floating-point representation.
+ ///
+ public float ToFloat() => Major + (Minor / 65536f);
+
+ ///
+ /// Returns the raw 32-bit Version16Dot16 value.
+ ///
+ public uint ToUInt32() => ((uint)Major << 16) | Minor;
+
+ public override string ToString()
+ {
+ // For common fractional values, show them nicely (e.g., 2.5 instead of 2.5000076)
+ if (Minor == 0)
+ return Major.ToString();
+ if (Minor == 0x8000) // 0.5
+ return $"{Major}.5";
+
+ return ToFloat().ToString("F6").TrimEnd('0').TrimEnd('.');
+ }
+
+ public static implicit operator float(FontVersion version) => version.ToFloat();
+
+ public static bool operator ==(FontVersion left, FontVersion right) =>
+ left.Major == right.Major && left.Minor == right.Minor;
+
+ public static bool operator !=(FontVersion left, FontVersion right) => !(left == right);
+
+ public override bool Equals(object? obj) => obj is FontVersion other && this == other;
+
+ public override int GetHashCode() => ((int)Major << 16) | Minor;
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/HeadTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/HeadTable.cs
new file mode 100644
index 0000000000..01f6831205
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/HeadTable.cs
@@ -0,0 +1,322 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Avalonia.Media.Fonts.Tables
+{
+ internal sealed class HeadTable
+ {
+ internal const string TableName = "head";
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
+
+ private static readonly DateTime s_fontEpoch = new DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ public FontVersion Version { get; }
+ public FontVersion FontRevision { get; }
+ public uint CheckSumAdjustment { get; }
+ public uint MagicNumber { get; }
+ public HeadFlags Flags { get; }
+ public ushort UnitsPerEm { get; }
+ public DateTime Created { get; }
+ public DateTime Modified { get; }
+ public short XMin { get; }
+ public short YMin { get; }
+ public short XMax { get; }
+ public short YMax { get; }
+ public MacStyleFlags MacStyle { get; }
+ public ushort LowestRecPPEM { get; }
+ public FontDirectionHint FontDirectionHint { get; }
+ public IndexToLocFormat IndexToLocFormat { get; }
+ public GlyphDataFormat GlyphDataFormat { get; }
+
+ private HeadTable(
+ FontVersion version,
+ FontVersion fontRevision,
+ uint checkSumAdjustment,
+ uint magicNumber,
+ HeadFlags flags,
+ ushort unitsPerEm,
+ DateTime created,
+ DateTime modified,
+ short xMin,
+ short yMin,
+ short xMax,
+ short yMax,
+ MacStyleFlags macStyle,
+ ushort lowestRecPPEM,
+ FontDirectionHint fontDirectionHint,
+ IndexToLocFormat indexToLocFormat,
+ GlyphDataFormat glyphDataFormat)
+ {
+ Version = version;
+ FontRevision = fontRevision;
+ CheckSumAdjustment = checkSumAdjustment;
+ MagicNumber = magicNumber;
+ Flags = flags;
+ UnitsPerEm = unitsPerEm;
+ Created = created;
+ Modified = modified;
+ XMin = xMin;
+ YMin = yMin;
+ XMax = xMax;
+ YMax = yMax;
+ MacStyle = macStyle;
+ LowestRecPPEM = lowestRecPPEM;
+ FontDirectionHint = fontDirectionHint;
+ IndexToLocFormat = indexToLocFormat;
+ GlyphDataFormat = glyphDataFormat;
+ }
+
+ public static bool TryLoad(GlyphTypeface glyphTypeface, [NotNullWhen(true)] out HeadTable? headTable)
+ {
+ headTable = null;
+
+ if (!glyphTypeface.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ return false;
+ }
+
+ var reader = new BigEndianBinaryReader(table.Span);
+ headTable = Load(ref reader);
+
+ return true;
+ }
+
+ private static HeadTable Load(ref BigEndianBinaryReader reader)
+ {
+ FontVersion version = reader.ReadVersion16Dot16();
+ FontVersion fontRevision = reader.ReadVersion16Dot16();
+ uint checkSumAdjustment = reader.ReadUInt32();
+ uint magicNumber = reader.ReadUInt32();
+ HeadFlags flags = (HeadFlags)reader.ReadUInt16();
+ ushort unitsPerEm = reader.ReadUInt16();
+ long createdRaw = reader.ReadInt64();
+ long modifiedRaw = reader.ReadInt64();
+ short xMin = reader.ReadInt16();
+ short yMin = reader.ReadInt16();
+ short xMax = reader.ReadInt16();
+ short yMax = reader.ReadInt16();
+ MacStyleFlags macStyle = (MacStyleFlags)reader.ReadUInt16();
+ ushort lowestRecPPEM = reader.ReadUInt16();
+ FontDirectionHint fontDirectionHint = (FontDirectionHint)reader.ReadInt16();
+ IndexToLocFormat indexToLocFormat = (IndexToLocFormat)reader.ReadInt16();
+ GlyphDataFormat glyphDataFormat = (GlyphDataFormat)reader.ReadInt16();
+
+ DateTime created = SafeAddSeconds(s_fontEpoch, createdRaw);
+ DateTime modified = SafeAddSeconds(s_fontEpoch, modifiedRaw);
+
+ return new HeadTable(
+ version,
+ fontRevision,
+ checkSumAdjustment,
+ magicNumber,
+ flags,
+ unitsPerEm,
+ created,
+ modified,
+ xMin,
+ yMin,
+ xMax,
+ yMax,
+ macStyle,
+ lowestRecPPEM,
+ fontDirectionHint,
+ indexToLocFormat,
+ glyphDataFormat);
+ }
+
+ private static DateTime SafeAddSeconds(DateTime epoch, long seconds)
+ {
+ // Handle invalid/corrupted timestamps gracefully
+ // Valid range for font timestamps is roughly 1904-01-01 to ~2040
+ // Negative values or extremely large values indicate corrupted data
+
+ try
+ {
+ // Check if the resulting date would be valid before attempting addition
+ // DateTime.MinValue is 0001-01-01, DateTime.MaxValue is 9999-12-31
+ if (seconds < 0)
+ {
+ // Calculate minimum allowed seconds from epoch to DateTime.MinValue
+ var minSeconds = (long)(DateTime.MinValue - epoch).TotalSeconds;
+ if (seconds < minSeconds)
+ {
+ return DateTime.MinValue;
+ }
+ }
+ else
+ {
+ // Calculate maximum allowed seconds from epoch to DateTime.MaxValue
+ var maxSeconds = (long)(DateTime.MaxValue - epoch).TotalSeconds;
+ if (seconds > maxSeconds)
+ {
+ return DateTime.MaxValue;
+ }
+ }
+
+ return epoch.AddSeconds(seconds);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ // Fallback for any edge cases that slip through
+ return seconds < 0 ? DateTime.MinValue : DateTime.MaxValue;
+ }
+ }
+ }
+
+ ///
+ /// Flags for the 'head' table.
+ ///
+ [Flags]
+ internal enum HeadFlags : ushort
+ {
+ ///
+ /// Bit 0: Baseline for font at y=0.
+ ///
+ BaselineAtY0 = 1 << 0,
+
+ ///
+ /// Bit 1: Left sidebearing point at x=0 (relevant only for TrueType rasterizers).
+ ///
+ LeftSidebearingAtX0 = 1 << 1,
+
+ ///
+ /// Bit 2: Instructions may depend on point size.
+ ///
+ InstructionsDependOnPointSize = 1 << 2,
+
+ ///
+ /// Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear.
+ ///
+ ForcePpemToInteger = 1 << 3,
+
+ ///
+ /// Bit 4: Instructions may alter advance width (the advance widths might not scale linearly).
+ ///
+ InstructionsMayAlterAdvanceWidth = 1 << 4,
+
+ ///
+ /// Bit 5: This bit should be set in fonts that are intended to be laid out vertically, and in which the glyphs have been drawn such that an x-coordinate of 0 corresponds to the desired vertical baseline.
+ ///
+ VerticalBaseline = 1 << 5,
+
+ ///
+ /// Bit 7: Font data is 'lossless' as a result of having been subjected to optimizing transformation and/or compression.
+ ///
+ Lossless = 1 << 7,
+
+ ///
+ /// Bit 8: Font converted (produce compatible metrics).
+ ///
+ FontConverted = 1 << 8,
+
+ ///
+ /// Bit 9: Font optimized for ClearType. Note that this implies that instructions may alter advance widths (bit 4 should also be set).
+ ///
+ ClearTypeOptimized = 1 << 9,
+
+ ///
+ /// Bit 10: Last Resort font. If set, indicates that the glyphs encoded in the 'cmap' subtables are simply generic symbolic representations of code point ranges and don't truly represent support for those code points.
+ ///
+ LastResortFont = 1 << 10,
+ }
+
+ ///
+ /// Mac style flags for font styling (used by macOS).
+ ///
+ [Flags]
+ internal enum MacStyleFlags : ushort
+ {
+ ///
+ /// Bit 0: Bold (if set to 1).
+ ///
+ Bold = 1 << 0,
+
+ ///
+ /// Bit 1: Italic (if set to 1).
+ ///
+ Italic = 1 << 1,
+
+ ///
+ /// Bit 2: Underline (if set to 1).
+ ///
+ Underline = 1 << 2,
+
+ ///
+ /// Bit 3: Outline (if set to 1).
+ ///
+ Outline = 1 << 3,
+
+ ///
+ /// Bit 4: Shadow (if set to 1).
+ ///
+ Shadow = 1 << 4,
+
+ ///
+ /// Bit 5: Condensed (if set to 1).
+ ///
+ Condensed = 1 << 5,
+
+ ///
+ /// Bit 6: Extended (if set to 1).
+ ///
+ Extended = 1 << 6,
+ }
+
+ ///
+ /// Specifies the format used for the 'loca' table.
+ ///
+ internal enum IndexToLocFormat : short
+ {
+ ///
+ /// Short offsets (Offset16). The actual local offset divided by 2 is stored.
+ ///
+ Short = 0,
+
+ ///
+ /// Long offsets (Offset32). The actual local offset is stored.
+ ///
+ Long = 1
+ }
+
+ ///
+ /// Specifies the format of glyph data.
+ ///
+ internal enum GlyphDataFormat : short
+ {
+ ///
+ /// Current format (TrueType outlines).
+ ///
+ Current = 0
+ }
+
+ ///
+ /// Font direction hint for mixed directional text.
+ ///
+ internal enum FontDirectionHint : short
+ {
+ ///
+ /// Fully mixed directional glyphs.
+ ///
+ FullyMixed = 0,
+
+ ///
+ /// Only strongly left to right glyphs.
+ ///
+ OnlyLeftToRight = 1,
+
+ ///
+ /// Like 1 but also contains neutrals.
+ ///
+ LeftToRightWithNeutrals = 2,
+
+ ///
+ /// Only strongly right to left glyphs.
+ ///
+ OnlyRightToLeft = -1,
+
+ ///
+ /// Like -1 but also contains neutrals.
+ ///
+ RightToLeftWithNeutrals = -2
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/HorizontalHeadTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/HorizontalHeaderTable.cs
similarity index 74%
rename from src/Avalonia.Base/Media/Fonts/Tables/HorizontalHeadTable.cs
rename to src/Avalonia.Base/Media/Fonts/Tables/HorizontalHeaderTable.cs
index 0942296536..9f6caf4955 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/HorizontalHeadTable.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/HorizontalHeaderTable.cs
@@ -2,16 +2,79 @@
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/blob/034a440aece357341fcc6b02db58ffbe153e54ef/src/SixLabors.Fonts
-using System.IO;
-
namespace Avalonia.Media.Fonts.Tables
{
- internal class HorizontalHeadTable
+ internal readonly struct HorizontalHeaderTable
{
internal const string TableName = "hhea";
- internal static OpenTypeTag Tag = OpenTypeTag.Parse(TableName);
- public HorizontalHeadTable(
+ ///
+ /// Gets the OpenType tag identifying this table ("hhea").
+ ///
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
+
+ ///
+ /// Gets the version of the horizontal header table.
+ ///
+ public FontVersion Version { get; }
+
+ ///
+ /// Gets the maximum advance width value for all glyphs in the font.
+ ///
+ public ushort AdvanceWidthMax { get; }
+
+ ///
+ /// Distance from the baseline to the highest ascender.
+ ///
+ public short Ascender { get; }
+
+ ///
+ /// Offset of the caret for slanted fonts. Set to 0 for non-slanted fonts.
+ ///
+ public short CaretOffset { get; }
+
+ ///
+ /// Rise component used to calculate the slope of the caret (rise/run).
+ ///
+ public short CaretSlopeRise { get; }
+
+ ///
+ /// Run component used to calculate the slope of the caret (rise/run).
+ ///
+ public short CaretSlopeRun { get; }
+
+ ///
+ /// Distance from the baseline to the lowest descender.
+ ///
+ public short Descender { get; }
+
+ ///
+ /// Typographic line gap.
+ ///
+ public short LineGap { get; }
+
+ ///
+ /// Minimum left side bearing value. Must be consistent with horizontal metrics.
+ ///
+ public short MinLeftSideBearing { get; }
+
+ ///
+ /// Minimum right side bearing value. Must be consistent with horizontal metrics.
+ ///
+ public short MinRightSideBearing { get; }
+
+ ///
+ /// Number of advance widths in the horizontal metrics table (numOfLongHorMetrics).
+ ///
+ public ushort NumberOfHMetrics { get; }
+
+ ///
+ /// Maximum horizontal extent: max(lsb + (xMax - xMin)).
+ ///
+ public short XMaxExtent { get; }
+
+ public HorizontalHeaderTable(
+ FontVersion version,
short ascender,
short descender,
short lineGap,
@@ -24,6 +87,7 @@ namespace Avalonia.Media.Fonts.Tables
short caretOffset,
ushort numberOfHMetrics)
{
+ Version = version;
Ascender = ascender;
Descender = descender;
LineGap = lineGap;
@@ -37,48 +101,28 @@ namespace Avalonia.Media.Fonts.Tables
NumberOfHMetrics = numberOfHMetrics;
}
- public ushort AdvanceWidthMax { get; }
-
- public short Ascender { get; }
-
- public short CaretOffset { get; }
-
- public short CaretSlopeRise { get; }
-
- public short CaretSlopeRun { get; }
-
- public short Descender { get; }
-
- public short LineGap { get; }
-
- public short MinLeftSideBearing { get; }
-
- public short MinRightSideBearing { get; }
-
- public ushort NumberOfHMetrics { get; }
-
- public short XMaxExtent { get; }
-
- public static HorizontalHeadTable? Load(IGlyphTypeface glyphTypeface)
+ public static bool TryLoad(GlyphTypeface fontFace, out HorizontalHeaderTable horizontalHeaderTable)
{
- if (!glyphTypeface.TryGetTable(Tag, out var table))
+ horizontalHeaderTable = default;
+
+ if (!fontFace.PlatformTypeface.TryGetTable(Tag, out var table))
{
- return null;
+ return false;
}
- using var stream = new MemoryStream(table);
- using var binaryReader = new BigEndianBinaryReader(stream, false);
+ var binaryReader = new BigEndianBinaryReader(table.Span);
- // Move to start of table.
- return Load(binaryReader);
+ return TryLoad(ref binaryReader, out horizontalHeaderTable);
}
- public static HorizontalHeadTable Load(BigEndianBinaryReader reader)
+ private static bool TryLoad(ref BigEndianBinaryReader reader, out HorizontalHeaderTable horizontalHeaderTable)
{
+ horizontalHeaderTable = default;
+
// +--------+---------------------+---------------------------------------------------------------------------------+
// | Type | Name | Description |
// +========+=====================+=================================================================================+
- // | Fixed | version | 0x00010000 (1.0) |
+ // | Version16Dot16 | version | 0x00010000 (1.0) |
// +--------+---------------------+---------------------------------------------------------------------------------+
// | FWord | ascent | Distance from baseline of highest ascender |
// +--------+---------------------+---------------------------------------------------------------------------------+
@@ -112,8 +156,7 @@ namespace Avalonia.Media.Fonts.Tables
// +--------+---------------------+---------------------------------------------------------------------------------+
// | uint16 | numOfLongHorMetrics | number of advance widths in metrics table |
// +--------+---------------------+---------------------------------------------------------------------------------+
- ushort majorVersion = reader.ReadUInt16();
- ushort minorVersion = reader.ReadUInt16();
+ FontVersion version = reader.ReadVersion16Dot16();
short ascender = reader.ReadFWORD();
short descender = reader.ReadFWORD();
short lineGap = reader.ReadFWORD();
@@ -129,14 +172,16 @@ namespace Avalonia.Media.Fonts.Tables
reader.ReadInt16(); // reserved
reader.ReadInt16(); // reserved
short metricDataFormat = reader.ReadInt16(); // 0
+
if (metricDataFormat != 0)
{
- throw new InvalidFontTableException($"Expected metricDataFormat = 0 found {metricDataFormat}", TableName);
+ return false;
}
ushort numberOfHMetrics = reader.ReadUInt16();
- return new HorizontalHeadTable(
+ horizontalHeaderTable = new HorizontalHeaderTable(
+ version,
ascender,
descender,
lineGap,
@@ -148,6 +193,8 @@ namespace Avalonia.Media.Fonts.Tables
caretSlopeRun,
caretOffset,
numberOfHMetrics);
+
+ return true;
}
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/MaxpTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/MaxpTable.cs
new file mode 100644
index 0000000000..6d6cbb288e
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/MaxpTable.cs
@@ -0,0 +1,138 @@
+using System;
+
+namespace Avalonia.Media.Fonts.Tables
+{
+ internal readonly struct MaxpTable
+ {
+ internal const string TableName = "maxp";
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
+
+ public FontVersion Version { get; }
+ public ushort NumGlyphs { get; }
+ public ushort MaxPoints { get; }
+ public ushort MaxContours { get; }
+ public ushort MaxCompositePoints { get; }
+ public ushort MaxCompositeContours { get; }
+ public ushort MaxZones { get; }
+ public ushort MaxTwilightPoints { get; }
+ public ushort MaxStorage { get; }
+ public ushort MaxFunctionDefs { get; }
+ public ushort MaxInstructionDefs { get; }
+ public ushort MaxStackElements { get; }
+ public ushort MaxSizeOfInstructions { get; }
+ public ushort MaxComponentElements { get; }
+ public ushort MaxComponentDepth { get; }
+
+ private MaxpTable(
+ FontVersion version,
+ ushort numGlyphs,
+ ushort maxPoints,
+ ushort maxContours,
+ ushort maxCompositePoints,
+ ushort maxCompositeContours,
+ ushort maxZones,
+ ushort maxTwilightPoints,
+ ushort maxStorage,
+ ushort maxFunctionDefs,
+ ushort maxInstructionDefs,
+ ushort maxStackElements,
+ ushort maxSizeOfInstructions,
+ ushort maxComponentElements,
+ ushort maxComponentDepth)
+ {
+ Version = version;
+ NumGlyphs = numGlyphs;
+ MaxPoints = maxPoints;
+ MaxContours = maxContours;
+ MaxCompositePoints = maxCompositePoints;
+ MaxCompositeContours = maxCompositeContours;
+ MaxZones = maxZones;
+ MaxTwilightPoints = maxTwilightPoints;
+ MaxStorage = maxStorage;
+ MaxFunctionDefs = maxFunctionDefs;
+ MaxInstructionDefs = maxInstructionDefs;
+ MaxStackElements = maxStackElements;
+ MaxSizeOfInstructions = maxSizeOfInstructions;
+ MaxComponentElements = maxComponentElements;
+ MaxComponentDepth = maxComponentDepth;
+ }
+
+ public static MaxpTable Load(GlyphTypeface fontFace)
+ {
+ if (!fontFace.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ throw new InvalidOperationException($"Could not load the '{TableName}' table.");
+ }
+
+ var binaryReader = new BigEndianBinaryReader(table.Span);
+
+ return Load(ref binaryReader);
+ }
+
+ private static MaxpTable Load(ref BigEndianBinaryReader reader)
+ {
+ // Version 0.5 (CFF/CFF2 fonts):
+ // | Version16Dot16 | version | 0x00005000 for version 0.5 |
+ // | uint16 | numGlyphs | The number of glyphs in the font|
+
+ // Version 1.0 (TrueType fonts):
+ // | Version16Dot16 | version | 0x00010000 for version 1.0 |
+ // | uint16 | numGlyphs | The number of glyphs in the font |
+ // | uint16 | maxPoints | Maximum points in a non-composite glyph |
+ // | uint16 | maxContours | Maximum contours in a non-composite glyph |
+ // | uint16 | maxCompositePoints | Maximum points in a composite glyph |
+ // | uint16 | maxCompositeContours | Maximum contours in a composite glyph |
+ // | uint16 | maxZones | 1 or 2; should be set to 2 in most cases |
+ // | uint16 | maxTwilightPoints | Maximum points used in Z0 |
+ // | uint16 | maxStorage | Number of Storage Area locations |
+ // | uint16 | maxFunctionDefs | Number of FDEFs |
+ // | uint16 | maxInstructionDefs | Number of IDEFs |
+ // | uint16 | maxStackElements | Maximum stack depth |
+ // | uint16 | maxSizeOfInstructions | Maximum byte count for glyph instructions |
+ // | uint16 | maxComponentElements | Maximum number of components at top level |
+ // | uint16 | maxComponentDepth | Maximum levels of recursion |
+
+ FontVersion version = reader.ReadVersion16Dot16();
+ ushort numGlyphs = reader.ReadUInt16();
+
+ if (version.Major < 1)
+ {
+ return new MaxpTable(
+ version,
+ numGlyphs,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+
+ ushort maxPoints = reader.ReadUInt16();
+ ushort maxContours = reader.ReadUInt16();
+ ushort maxCompositePoints = reader.ReadUInt16();
+ ushort maxCompositeContours = reader.ReadUInt16();
+ ushort maxZones = reader.ReadUInt16();
+ ushort maxTwilightPoints = reader.ReadUInt16();
+ ushort maxStorage = reader.ReadUInt16();
+ ushort maxFunctionDefs = reader.ReadUInt16();
+ ushort maxInstructionDefs = reader.ReadUInt16();
+ ushort maxStackElements = reader.ReadUInt16();
+ ushort maxSizeOfInstructions = reader.ReadUInt16();
+ ushort maxComponentElements = reader.ReadUInt16();
+ ushort maxComponentDepth = reader.ReadUInt16();
+
+ return new MaxpTable(
+ version,
+ numGlyphs,
+ maxPoints,
+ maxContours,
+ maxCompositePoints,
+ maxCompositeContours,
+ maxZones,
+ maxTwilightPoints,
+ maxStorage,
+ maxFunctionDefs,
+ maxInstructionDefs,
+ maxStackElements,
+ maxSizeOfInstructions,
+ maxComponentElements,
+ maxComponentDepth);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Metrics/HorizontalGlyphMetric.cs b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/HorizontalGlyphMetric.cs
new file mode 100644
index 0000000000..7de0a0a278
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/HorizontalGlyphMetric.cs
@@ -0,0 +1,26 @@
+namespace Avalonia.Media.Fonts.Tables.Metrics
+{
+ ///
+ /// Represents a single horizontal metric record from the 'hmtx' table.
+ ///
+ internal readonly record struct HorizontalGlyphMetric
+ {
+ ///
+ /// The advance width of the glyph.
+ ///
+ public ushort AdvanceWidth { get; }
+
+ ///
+ /// The left side bearing of the glyph.
+ ///
+ public short LeftSideBearing { get; }
+
+ public HorizontalGlyphMetric(ushort advanceWidth, short leftSideBearing)
+ {
+ AdvanceWidth = advanceWidth;
+ LeftSideBearing = leftSideBearing;
+ }
+
+ public override string ToString() => $"Advance={AdvanceWidth}, LSB={LeftSideBearing}";
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Metrics/HorizontalMetricsTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/HorizontalMetricsTable.cs
new file mode 100644
index 0000000000..9154284a14
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/HorizontalMetricsTable.cs
@@ -0,0 +1,227 @@
+using System;
+
+namespace Avalonia.Media.Fonts.Tables.Metrics
+{
+ internal class HorizontalMetricsTable
+ {
+ public const string TagName = "hmtx";
+ public static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TagName);
+
+ private readonly ReadOnlyMemory _data;
+ private readonly ushort _numOfHMetrics;
+ private readonly int _numGlyphs;
+
+ private HorizontalMetricsTable(ReadOnlyMemory data, ushort numOfHMetrics, int numGlyphs)
+ {
+ _data = data;
+ _numOfHMetrics = numOfHMetrics;
+ _numGlyphs = numGlyphs;
+ }
+
+ internal static HorizontalMetricsTable? Load(GlyphTypeface glyphTypeface, ushort numberOfHMetrics, int glyphCount)
+ {
+ if (glyphTypeface.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ return new HorizontalMetricsTable(table, numberOfHMetrics, glyphCount);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Attempts to retrieve the horizontal glyph metrics for the specified glyph index.
+ ///
+ /// The index of the glyph for which to retrieve metrics.
+ /// When this method returns, contains the horizontal glyph metric if the glyph index is valid; otherwise, the default value.
+ /// true if the glyph index is valid and metrics were retrieved; otherwise, false.
+ public bool TryGetMetrics(ushort glyphIndex, out HorizontalGlyphMetric metric)
+ {
+ metric = default;
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ var reader = new BigEndianBinaryReader(_data.Span);
+
+ if (glyphIndex < _numOfHMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+
+ ushort advanceWidth = reader.ReadUInt16();
+ short leftSideBearing = reader.ReadInt16();
+
+ metric = new HorizontalGlyphMetric(advanceWidth, leftSideBearing);
+ }
+ else
+ {
+ reader.Seek((_numOfHMetrics - 1) * 4);
+
+ ushort lastAdvanceWidth = reader.ReadUInt16();
+
+ int lsbIndex = glyphIndex - _numOfHMetrics;
+ int lsbOffset = _numOfHMetrics * 4 + lsbIndex * 2;
+
+ reader.Seek(lsbOffset);
+
+ short leftSideBearing = reader.ReadInt16();
+
+ metric = new HorizontalGlyphMetric(lastAdvanceWidth, leftSideBearing);
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to retrieve the advance width for a single glyph.
+ ///
+ /// Glyph index to query.
+ /// When this method returns, contains the advance width if the glyph index is valid; otherwise, zero.
+ /// true if the glyph index is valid and the advance was retrieved; otherwise, false.
+ public bool TryGetAdvance(ushort glyphIndex, out ushort advance)
+ {
+ advance = 0;
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ var reader = new BigEndianBinaryReader(_data.Span);
+
+ if (glyphIndex < _numOfHMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+
+ advance = reader.ReadUInt16();
+ }
+ else
+ {
+ reader.Seek((_numOfHMetrics - 1) * 4);
+
+ advance = reader.ReadUInt16();
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to retrieve advance widths for multiple glyphs in a single operation.
+ ///
+ /// Read-only span of glyph indices to query.
+ /// Output span to write the advance widths. Must be at least as long as .
+ /// true if all glyph indices are valid and advances were retrieved; otherwise, false.
+ ///
+ /// This method is more efficient than calling multiple times as it reuses
+ /// the same reader and span reference. If any glyph index is invalid, the method returns false
+ /// and the contents of are undefined.
+ ///
+ public bool TryGetAdvances(ReadOnlySpan glyphIndices, Span advances)
+ {
+ if (advances.Length < glyphIndices.Length)
+ {
+ return false;
+ }
+
+ var data = _data.Span;
+ var reader = new BigEndianBinaryReader(data);
+
+ // Cache the last advance width for glyphs beyond numOfHMetrics
+ ushort? lastAdvanceWidth = null;
+
+ for (int i = 0; i < glyphIndices.Length; i++)
+ {
+ ushort glyphIndex = glyphIndices[i];
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ if (glyphIndex < _numOfHMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+ advances[i] = reader.ReadUInt16();
+ }
+ else
+ {
+ // All glyphs beyond numOfHMetrics share the same advance width
+ if (!lastAdvanceWidth.HasValue)
+ {
+ reader.Seek((_numOfHMetrics - 1) * 4);
+ lastAdvanceWidth = reader.ReadUInt16();
+ }
+
+ advances[i] = lastAdvanceWidth.Value;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to retrieve horizontal glyph metrics for multiple glyphs in a single operation.
+ ///
+ /// Read-only span of glyph indices to query.
+ /// Output span to write the metrics. Must be at least as long as .
+ /// true if all glyph indices are valid and metrics were retrieved; otherwise, false.
+ ///
+ /// This method is more efficient than calling multiple times as it reuses
+ /// the same reader and span reference. If any glyph index is invalid, the method returns false
+ /// and the contents of are undefined.
+ ///
+ public bool TryGetMetrics(ReadOnlySpan glyphIndices, Span metrics)
+ {
+ if (metrics.Length < glyphIndices.Length)
+ {
+ return false;
+ }
+
+ var data = _data.Span;
+ var reader = new BigEndianBinaryReader(data);
+
+ // Cache the last advance width for glyphs beyond numOfHMetrics
+ ushort? lastAdvanceWidth = null;
+
+ for (int i = 0; i < glyphIndices.Length; i++)
+ {
+ ushort glyphIndex = glyphIndices[i];
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ if (glyphIndex < _numOfHMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+
+ ushort advanceWidth = reader.ReadUInt16();
+ short leftSideBearing = reader.ReadInt16();
+
+ metrics[i] = new HorizontalGlyphMetric(advanceWidth, leftSideBearing);
+ }
+ else
+ {
+ // All glyphs beyond numOfHMetrics share the same advance width
+ if (!lastAdvanceWidth.HasValue)
+ {
+ reader.Seek((_numOfHMetrics - 1) * 4);
+ lastAdvanceWidth = reader.ReadUInt16();
+ }
+
+ int lsbIndex = glyphIndex - _numOfHMetrics;
+ int lsbOffset = _numOfHMetrics * 4 + lsbIndex * 2;
+
+ reader.Seek(lsbOffset);
+ short leftSideBearing = reader.ReadInt16();
+
+ metrics[i] = new HorizontalGlyphMetric(lastAdvanceWidth.Value, leftSideBearing);
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Metrics/VerticalGlyphMetric.cs b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/VerticalGlyphMetric.cs
new file mode 100644
index 0000000000..ef252ad6eb
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/VerticalGlyphMetric.cs
@@ -0,0 +1,24 @@
+namespace Avalonia.Media.Fonts.Tables.Metrics
+{
+ ///
+ /// Represents a single vertical metric record from the 'vmtx' table.
+ ///
+ internal readonly record struct VerticalGlyphMetric
+ {
+ public VerticalGlyphMetric(ushort advanceHeight, short topSideBearing)
+ {
+ AdvanceHeight = advanceHeight;
+ TopSideBearing = topSideBearing;
+ }
+
+ ///
+ /// The advance height of the glyph.
+ ///
+ public ushort AdvanceHeight { get; }
+
+ ///
+ /// The top side bearing of the glyph.
+ ///
+ public short TopSideBearing { get; }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Metrics/VerticalMetricsTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/VerticalMetricsTable.cs
new file mode 100644
index 0000000000..c5fdda1a74
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Metrics/VerticalMetricsTable.cs
@@ -0,0 +1,227 @@
+using System;
+
+namespace Avalonia.Media.Fonts.Tables.Metrics
+{
+ internal class VerticalMetricsTable
+ {
+ public const string TagName = "vmtx";
+ public static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TagName);
+
+ private readonly ReadOnlyMemory _data;
+ private readonly ushort _numOfVMetrics;
+ private readonly int _numGlyphs;
+
+ private VerticalMetricsTable(ReadOnlyMemory data, ushort numOfVMetrics, int numGlyphs)
+ {
+ _data = data;
+ _numOfVMetrics = numOfVMetrics;
+ _numGlyphs = numGlyphs;
+ }
+
+ public static VerticalMetricsTable? Load(GlyphTypeface glyphTypeface, ushort numberOfVMetrics, int glyphCount)
+ {
+ if (glyphTypeface.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ return new VerticalMetricsTable(table, numberOfVMetrics, glyphCount);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Attempts to retrieve the vertical glyph metrics for the specified glyph index.
+ ///
+ /// The index of the glyph for which to retrieve metrics.
+ /// When this method returns, contains the vertical glyph metric if the glyph index is valid; otherwise, the default value.
+ /// true if the glyph index is valid and metrics were retrieved; otherwise, false.
+ public bool TryGetMetrics(ushort glyphIndex, out VerticalGlyphMetric metric)
+ {
+ metric = default;
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ var reader = new BigEndianBinaryReader(_data.Span);
+
+ if (glyphIndex < _numOfVMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+
+ ushort advanceHeight = reader.ReadUInt16();
+ short topSideBearing = reader.ReadInt16();
+
+ metric = new VerticalGlyphMetric(advanceHeight, topSideBearing);
+ }
+ else
+ {
+ reader.Seek((_numOfVMetrics - 1) * 4);
+
+ ushort lastAdvanceHeight = reader.ReadUInt16();
+
+ int tsbIndex = glyphIndex - _numOfVMetrics;
+ int tsbOffset = _numOfVMetrics * 4 + tsbIndex * 2;
+
+ reader.Seek(tsbOffset);
+
+ short tsb = reader.ReadInt16();
+
+ metric = new VerticalGlyphMetric(lastAdvanceHeight, tsb);
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to retrieve the advance height for a single glyph.
+ ///
+ /// Glyph index to query.
+ /// When this method returns, contains the advance height if the glyph index is valid; otherwise, zero.
+ /// true if the glyph index is valid and the advance was retrieved; otherwise, false.
+ public bool TryGetAdvance(ushort glyphIndex, out ushort advance)
+ {
+ advance = 0;
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ var reader = new BigEndianBinaryReader(_data.Span);
+
+ if (glyphIndex < _numOfVMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+
+ advance = reader.ReadUInt16();
+ }
+ else
+ {
+ reader.Seek((_numOfVMetrics - 1) * 4);
+
+ advance = reader.ReadUInt16();
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to retrieve advance heights for multiple glyphs in a single operation.
+ ///
+ /// Read-only span of glyph indices to query.
+ /// Output span to write the advance heights. Must be at least as long as .
+ /// true if all glyph indices are valid and advances were retrieved; otherwise, false.
+ ///
+ /// This method is more efficient than calling multiple times as it reuses
+ /// the same reader and span reference. If any glyph index is invalid, the method returns false
+ /// and the contents of are undefined.
+ ///
+ public bool TryGetAdvances(ReadOnlySpan glyphIndices, Span advances)
+ {
+ if (advances.Length < glyphIndices.Length)
+ {
+ return false;
+ }
+
+ var data = _data.Span;
+ var reader = new BigEndianBinaryReader(data);
+
+ // Cache the last advance height for glyphs beyond numOfVMetrics
+ ushort? lastAdvanceHeight = null;
+
+ for (int i = 0; i < glyphIndices.Length; i++)
+ {
+ ushort glyphIndex = glyphIndices[i];
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ if (glyphIndex < _numOfVMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+ advances[i] = reader.ReadUInt16();
+ }
+ else
+ {
+ // All glyphs beyond numOfVMetrics share the same advance height
+ if (!lastAdvanceHeight.HasValue)
+ {
+ reader.Seek((_numOfVMetrics - 1) * 4);
+ lastAdvanceHeight = reader.ReadUInt16();
+ }
+
+ advances[i] = lastAdvanceHeight.Value;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to retrieve vertical glyph metrics for multiple glyphs in a single operation.
+ ///
+ /// Read-only span of glyph indices to query.
+ /// Output span to write the metrics. Must be at least as long as .
+ /// true if all glyph indices are valid and metrics were retrieved; otherwise, false.
+ ///
+ /// This method is more efficient than calling multiple times as it reuses
+ /// the same reader and span reference. If any glyph index is invalid, the method returns false
+ /// and the contents of are undefined.
+ ///
+ public bool TryGetMetrics(ReadOnlySpan glyphIndices, Span metrics)
+ {
+ if (metrics.Length < glyphIndices.Length)
+ {
+ return false;
+ }
+
+ var data = _data.Span;
+ var reader = new BigEndianBinaryReader(data);
+
+ // Cache the last advance height for glyphs beyond numOfVMetrics
+ ushort? lastAdvanceHeight = null;
+
+ for (int i = 0; i < glyphIndices.Length; i++)
+ {
+ ushort glyphIndex = glyphIndices[i];
+
+ if (glyphIndex >= _numGlyphs)
+ {
+ return false;
+ }
+
+ if (glyphIndex < _numOfVMetrics)
+ {
+ reader.Seek(glyphIndex * 4);
+
+ ushort advanceHeight = reader.ReadUInt16();
+ short topSideBearing = reader.ReadInt16();
+
+ metrics[i] = new VerticalGlyphMetric(advanceHeight, topSideBearing);
+ }
+ else
+ {
+ // All glyphs beyond numOfVMetrics share the same advance height
+ if (!lastAdvanceHeight.HasValue)
+ {
+ reader.Seek((_numOfVMetrics - 1) * 4);
+ lastAdvanceHeight = reader.ReadUInt16();
+ }
+
+ int tsbIndex = glyphIndex - _numOfVMetrics;
+ int tsbOffset = _numOfVMetrics * 4 + tsbIndex * 2;
+
+ reader.Seek(tsbOffset);
+ short topSideBearing = reader.ReadInt16();
+
+ metrics[i] = new VerticalGlyphMetric(lastAdvanceHeight.Value, topSideBearing);
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Name/NameRecord.cs b/src/Avalonia.Base/Media/Fonts/Tables/Name/NameRecord.cs
index 7a7ad71995..794113674d 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/Name/NameRecord.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Name/NameRecord.cs
@@ -2,44 +2,55 @@
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/blob/034a440aece357341fcc6b02db58ffbe153e54ef/src/SixLabors.Fonts
+using System;
+
namespace Avalonia.Media.Fonts.Tables.Name
{
- internal class NameRecord
+ internal readonly struct NameRecord
{
- private readonly string value;
-
- public NameRecord(PlatformIDs platform, ushort languageId, KnownNameIds nameId, string value)
+ private readonly ReadOnlyMemory _stringStorage;
+
+ public NameRecord(
+ ReadOnlyMemory stringStorage,
+ PlatformID platform,
+ ushort languageId,
+ KnownNameIds nameId,
+ ushort offset,
+ ushort length,
+ System.Text.Encoding encoding)
{
+ _stringStorage = stringStorage;
+
Platform = platform;
LanguageID = languageId;
NameID = nameId;
- this.value = value;
+ Offset = offset;
+ Length = length;
+ Encoding = encoding;
}
- public PlatformIDs Platform { get; }
+ public PlatformID Platform { get; }
public ushort LanguageID { get; }
public KnownNameIds NameID { get; }
- internal StringLoader? StringReader { get; private set; }
+ public ushort Offset { get; }
- public string Value => StringReader?.Value ?? value;
+ public ushort Length { get; }
- public static NameRecord Read(BigEndianBinaryReader reader)
+ public System.Text.Encoding Encoding { get; }
+
+ public string GetValue()
{
- var platform = reader.ReadUInt16();
- var encodingId = reader.ReadUInt16();
- var encoding = encodingId.AsEncoding();
- var languageID = reader.ReadUInt16();
- var nameID = reader.ReadUInt16();
+ if (Length == 0)
+ {
+ return string.Empty;
+ }
- var stringReader = StringLoader.Create(reader, encoding);
+ var span = _stringStorage.Span.Slice(Offset, Length);
- return new NameRecord(platform, languageID, nameID, string.Empty)
- {
- StringReader = stringReader
- };
+ return Encoding.GetString(span);
}
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Name/NameTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/Name/NameTable.cs
index c0c1048e51..f58d6a2551 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/Name/NameTable.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Name/NameTable.cs
@@ -4,7 +4,6 @@
using System.Collections;
using System.Collections.Generic;
-using System.IO;
using Avalonia.Utilities;
namespace Avalonia.Media.Fonts.Tables.Name
@@ -14,7 +13,11 @@ namespace Avalonia.Media.Fonts.Tables.Name
internal const string TableName = "name";
internal static readonly OpenTypeTag Tag = OpenTypeTag.Parse(TableName);
+ private const ushort USEnglishLanguageId = 0x0409;
+
private readonly NameRecord[] _names;
+ private string? _cachedFamilyName;
+ private string? _cachedTypographicFamilyName;
internal NameTable(NameRecord[] names)
{
@@ -46,7 +49,21 @@ namespace Avalonia.Media.Fonts.Tables.Name
/// The name of the font.
///
public string FontFamilyName(ushort culture)
- => GetNameById(culture, KnownNameIds.FontFamilyName);
+ {
+ if (culture == USEnglishLanguageId && _cachedFamilyName is not null)
+ {
+ return _cachedFamilyName;
+ }
+
+ var value = GetNameById(culture, KnownNameIds.FontFamilyName);
+
+ if (culture == USEnglishLanguageId)
+ {
+ _cachedFamilyName = value;
+ }
+
+ return value;
+ }
///
/// Gets the name of the font.
@@ -59,86 +76,79 @@ namespace Avalonia.Media.Fonts.Tables.Name
public string GetNameById(ushort culture, KnownNameIds nameId)
{
+ if (nameId == KnownNameIds.TypographicFamilyName && culture == USEnglishLanguageId && _cachedTypographicFamilyName is not null)
+ {
+ return _cachedTypographicFamilyName;
+ }
+
var languageId = culture;
NameRecord? usaVersion = null;
NameRecord? firstWindows = null;
NameRecord? first = null;
+
foreach (var name in _names)
{
if (name.NameID == nameId)
{
- // Get just the first one, just in case.
first ??= name;
- if (name.Platform == PlatformIDs.Windows)
+ if (name.Platform == PlatformID.Windows)
{
- // If us not found return the first windows one.
firstWindows ??= name;
- if (name.LanguageID == 0x0409)
+ if (name.LanguageID == USEnglishLanguageId)
{
- // Grab the us version as its on next best match.
usaVersion ??= name;
}
if (name.LanguageID == languageId)
{
- // Return the most exact first.
- return name.Value;
+ return name.GetValue();
}
}
}
}
- return usaVersion?.Value ??
- firstWindows?.Value ??
- first?.Value ??
- string.Empty;
+ var value = usaVersion?.GetValue() ??
+ firstWindows?.GetValue() ??
+ first?.GetValue() ??
+ string.Empty;
+
+ if (nameId == KnownNameIds.TypographicFamilyName && culture == USEnglishLanguageId)
+ {
+ _cachedTypographicFamilyName = value;
+ }
+
+ return value;
}
public string GetNameById(ushort culture, ushort nameId)
=> GetNameById(culture, (KnownNameIds)nameId);
- public static NameTable? Load(IGlyphTypeface glyphTypeface)
+ public static NameTable? Load(GlyphTypeface glyphTypeface)
{
- if (!glyphTypeface.TryGetTable(Tag, out var table))
+ if (!glyphTypeface.PlatformTypeface.TryGetTable(Tag, out var table))
{
return null;
}
- using var stream = new MemoryStream(table);
- using var binaryReader = new BigEndianBinaryReader(stream, false);
-
- // Move to start of table.
- return Load(binaryReader);
- }
+ var reader = new BigEndianBinaryReader(table.Span);
- public static NameTable Load(BigEndianBinaryReader reader)
- {
- var strings = new List();
- var format = reader.ReadUInt16();
- var nameCount = reader.ReadUInt16();
- var stringOffset = reader.ReadUInt16();
+ reader.ReadUInt16();
+ var count = reader.ReadUInt16();
+ var storageOffset = reader.ReadUInt16();
- var names = new NameRecord[nameCount];
+ var names = new NameRecord[count];
- for (var i = 0; i < nameCount; i++)
+ for (var i = 0; i < count; i++)
{
- names[i] = NameRecord.Read(reader);
-
- var sr = names[i].StringReader;
-
- if (sr is not null)
- {
- strings.Add(sr);
- }
- }
-
- foreach (var readable in strings)
- {
- var readableStartOffset = stringOffset + readable.Offset;
-
- reader.Seek(readableStartOffset, SeekOrigin.Begin);
-
- readable.LoadValue(reader);
+ var platform = reader.ReadUInt16();
+ var encodingId = reader.ReadUInt16();
+ var encoding = encodingId.AsEncoding();
+ var languageID = reader.ReadUInt16();
+ var nameID = reader.ReadUInt16();
+ var length = reader.ReadUInt16();
+ var offset = reader.ReadUInt16();
+
+ names[i] = new NameRecord(table.Slice(storageOffset), platform, languageID, nameID, offset, length, encoding);
}
return new NameTable(names);
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs b/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs
index 9dc41ef083..5f5dc6c025 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs
@@ -3,338 +3,199 @@
// Ported from: https://github.com/SixLabors/Fonts/blob/034a440aece357341fcc6b02db58ffbe153e54ef/src/SixLabors.Fonts
using System;
-using System.IO;
namespace Avalonia.Media.Fonts.Tables
{
- internal sealed class OS2Table
+ internal readonly struct OS2Table
{
internal const string TableName = "OS/2";
- internal static OpenTypeTag Tag = OpenTypeTag.Parse(TableName);
-
- private readonly byte[] panose;
- private readonly short capHeight;
- private readonly short familyClass;
- private readonly short heightX;
- private readonly string tag;
- private readonly ushort codePageRange1;
- private readonly ushort codePageRange2;
- private readonly uint unicodeRange1;
- private readonly uint unicodeRange2;
- private readonly uint unicodeRange3;
- private readonly uint unicodeRange4;
- private readonly ushort breakChar;
- private readonly ushort defaultChar;
- private readonly ushort firstCharIndex;
- private readonly ushort lastCharIndex;
- private readonly ushort lowerOpticalPointSize;
- private readonly ushort maxContext;
- private readonly ushort upperOpticalPointSize;
- private readonly short averageCharWidth;
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
- public OS2Table(
- short averageCharWidth,
+ [Flags]
+ internal enum FontSelectionFlags : ushort
+ {
+ ITALIC = 1,
+ UNDERSCORE = 1 << 1,
+ NEGATIVE = 1 << 2,
+ OUTLINED = 1 << 3,
+ STRIKEOUT = 1 << 4,
+ BOLD = 1 << 5,
+ REGULAR = 1 << 6,
+ USE_TYPO_METRICS = 1 << 7,
+ WWS = 1 << 8,
+ OBLIQUE = 1 << 9,
+ }
+
+ public ushort Version { get; }
+ public short XAvgCharWidth { get; }
+ public ushort WeightClass { get; }
+ public ushort WidthClass { get; }
+ public ushort FsType { get; }
+ public short YSubscriptXSize { get; }
+ public short YSubscriptYSize { get; }
+ public short YSubscriptXOffset { get; }
+ public short YSubscriptYOffset { get; }
+ public short YSuperscriptXSize { get; }
+ public short YSuperscriptYSize { get; }
+ public short YSuperscriptXOffset { get; }
+ public short YSuperscriptYOffset { get; }
+ public short StrikeoutSize { get; }
+ public short StrikeoutPosition { get; }
+ public short FamilyClass { get; }
+ public Panose Panose { get; }
+ public uint UnicodeRange1 { get; }
+ public uint UnicodeRange2 { get; }
+ public uint UnicodeRange3 { get; }
+ public uint UnicodeRange4 { get; }
+ public uint VendorId { get; }
+ public FontSelectionFlags Selection { get; }
+ public ushort FirstCharIndex { get; }
+ public ushort LastCharIndex { get; }
+ public short TypoAscender { get; }
+ public short TypoDescender { get; }
+ public short TypoLineGap { get; }
+ public ushort WinAscent { get; }
+ public ushort WinDescent { get; }
+
+ public uint CodePageRange1 { get; }
+ public uint CodePageRange2 { get; }
+
+ public short XHeight { get; }
+ public short CapHeight { get; }
+ public ushort DefaultChar { get; }
+ public ushort BreakChar { get; }
+ public ushort MaxContext { get; }
+
+ public ushort LowerOpticalPointSize { get; }
+ public ushort UpperOpticalPointSize { get; }
+
+ private OS2Table(
+ ushort version,
+ short xAvgCharWidth,
ushort weightClass,
ushort widthClass,
- ushort styleType,
- short subscriptXSize,
- short subscriptYSize,
- short subscriptXOffset,
- short subscriptYOffset,
- short superscriptXSize,
- short superscriptYSize,
- short superscriptXOffset,
- short superscriptYOffset,
+ ushort fsType,
+ short ySubscriptXSize,
+ short ySubscriptYSize,
+ short ySubscriptXOffset,
+ short ySubscriptYOffset,
+ short ySuperscriptXSize,
+ short ySuperscriptYSize,
+ short ySuperscriptXOffset,
+ short ySuperscriptYOffset,
short strikeoutSize,
short strikeoutPosition,
short familyClass,
- byte[] panose,
+ Panose panose,
uint unicodeRange1,
uint unicodeRange2,
uint unicodeRange3,
uint unicodeRange4,
- string tag,
- FontStyleSelection fontStyle,
+ uint vendorId,
+ FontSelectionFlags selection,
ushort firstCharIndex,
ushort lastCharIndex,
short typoAscender,
short typoDescender,
short typoLineGap,
ushort winAscent,
- ushort winDescent)
+ ushort winDescent,
+ uint codePageRange1,
+ uint codePageRange2,
+ short xHeight,
+ short capHeight,
+ ushort defaultChar,
+ ushort breakChar,
+ ushort maxContext,
+ ushort lowerOpticalPointSize,
+ ushort upperOpticalPointSize)
{
- this.averageCharWidth = averageCharWidth;
+ Version = version;
+ XAvgCharWidth = xAvgCharWidth;
WeightClass = weightClass;
WidthClass = widthClass;
- StyleType = styleType;
- SubscriptXSize = subscriptXSize;
- SubscriptYSize = subscriptYSize;
- SubscriptXOffset = subscriptXOffset;
- SubscriptYOffset = subscriptYOffset;
- SuperscriptXSize = superscriptXSize;
- SuperscriptYSize = superscriptYSize;
- SuperscriptXOffset = superscriptXOffset;
- SuperscriptYOffset = superscriptYOffset;
+ FsType = fsType;
+ YSubscriptXSize = ySubscriptXSize;
+ YSubscriptYSize = ySubscriptYSize;
+ YSubscriptXOffset = ySubscriptXOffset;
+ YSubscriptYOffset = ySubscriptYOffset;
+ YSuperscriptXSize = ySuperscriptXSize;
+ YSuperscriptYSize = ySuperscriptYSize;
+ YSuperscriptXOffset = ySuperscriptXOffset;
+ YSuperscriptYOffset = ySuperscriptYOffset;
StrikeoutSize = strikeoutSize;
StrikeoutPosition = strikeoutPosition;
- this.familyClass = familyClass;
- this.panose = panose;
- this.unicodeRange1 = unicodeRange1;
- this.unicodeRange2 = unicodeRange2;
- this.unicodeRange3 = unicodeRange3;
- this.unicodeRange4 = unicodeRange4;
- this.tag = tag;
- FontStyle = fontStyle;
- this.firstCharIndex = firstCharIndex;
- this.lastCharIndex = lastCharIndex;
+ FamilyClass = familyClass;
+ Panose = panose;
+ UnicodeRange1 = unicodeRange1;
+ UnicodeRange2 = unicodeRange2;
+ UnicodeRange3 = unicodeRange3;
+ UnicodeRange4 = unicodeRange4;
+ VendorId = vendorId;
+ Selection = selection;
+ FirstCharIndex = firstCharIndex;
+ LastCharIndex = lastCharIndex;
TypoAscender = typoAscender;
TypoDescender = typoDescender;
TypoLineGap = typoLineGap;
WinAscent = winAscent;
WinDescent = winDescent;
+ CodePageRange1 = codePageRange1;
+ CodePageRange2 = codePageRange2;
+ XHeight = xHeight;
+ CapHeight = capHeight;
+ DefaultChar = defaultChar;
+ BreakChar = breakChar;
+ MaxContext = maxContext;
+ LowerOpticalPointSize = lowerOpticalPointSize;
+ UpperOpticalPointSize = upperOpticalPointSize;
}
- public OS2Table(
- OS2Table version0Table,
- ushort codePageRange1,
- ushort codePageRange2,
- short heightX,
- short capHeight,
- ushort defaultChar,
- ushort breakChar,
- ushort maxContext)
- : this(
- version0Table.averageCharWidth,
- version0Table.WeightClass,
- version0Table.WidthClass,
- version0Table.StyleType,
- version0Table.SubscriptXSize,
- version0Table.SubscriptYSize,
- version0Table.SubscriptXOffset,
- version0Table.SubscriptYOffset,
- version0Table.SuperscriptXSize,
- version0Table.SuperscriptYSize,
- version0Table.SuperscriptXOffset,
- version0Table.SuperscriptYOffset,
- version0Table.StrikeoutSize,
- version0Table.StrikeoutPosition,
- version0Table.familyClass,
- version0Table.panose,
- version0Table.unicodeRange1,
- version0Table.unicodeRange2,
- version0Table.unicodeRange3,
- version0Table.unicodeRange4,
- version0Table.tag,
- version0Table.FontStyle,
- version0Table.firstCharIndex,
- version0Table.lastCharIndex,
- version0Table.TypoAscender,
- version0Table.TypoDescender,
- version0Table.TypoLineGap,
- version0Table.WinAscent,
- version0Table.WinDescent)
+ public static bool TryLoad(GlyphTypeface fontFace, out OS2Table os2Table)
{
- this.codePageRange1 = codePageRange1;
- this.codePageRange2 = codePageRange2;
- this.heightX = heightX;
- this.capHeight = capHeight;
- this.defaultChar = defaultChar;
- this.breakChar = breakChar;
- this.maxContext = maxContext;
- }
-
- public OS2Table(OS2Table versionLessThan5Table, ushort lowerOpticalPointSize, ushort upperOpticalPointSize)
- : this(
- versionLessThan5Table,
- versionLessThan5Table.codePageRange1,
- versionLessThan5Table.codePageRange2,
- versionLessThan5Table.heightX,
- versionLessThan5Table.capHeight,
- versionLessThan5Table.defaultChar,
- versionLessThan5Table.breakChar,
- versionLessThan5Table.maxContext)
- {
- this.lowerOpticalPointSize = lowerOpticalPointSize;
- this.upperOpticalPointSize = upperOpticalPointSize;
- }
-
- [Flags]
- internal enum FontStyleSelection : ushort
- {
- ///
- /// Font contains italic or oblique characters, otherwise they are upright.
- ///
- ITALIC = 1,
-
- ///
- /// Characters are underscored.
- ///
- UNDERSCORE = 1 << 1,
-
- ///
- /// Characters have their foreground and background reversed.
- ///
- NEGATIVE = 1 << 2,
-
- ///
- /// characters, otherwise they are solid.
- ///
- OUTLINED = 1 << 3,
-
- ///
- /// Characters are overstruck.
- ///
- STRIKEOUT = 1 << 4,
-
- ///
- /// Characters are emboldened.
- ///
- BOLD = 1 << 5,
-
- ///
- /// Characters are in the standard weight/style for the font.
- ///
- REGULAR = 1 << 6,
-
- ///
- /// If set, it is strongly recommended to use OS/2.typoAscender - OS/2.typoDescender+ OS/2.typoLineGap as a value for default line spacing for this font.
- ///
- USE_TYPO_METRICS = 1 << 7,
-
- ///
- /// The font has ‘name’ table strings consistent with a weight/width/slope family without requiring use of ‘name’ IDs 21 and 22. (Please see more detailed description below.)
- ///
- WWS = 1 << 8,
-
- ///
- /// Font contains oblique characters.
- ///
- OBLIQUE = 1 << 9,
-
- // 10–15 Reserved; set to 0.
- }
-
- public FontStyleSelection FontStyle { get; }
-
- public short TypoAscender { get; }
-
- public short TypoDescender { get; }
-
- public short TypoLineGap { get; }
-
- public ushort WinAscent { get; }
-
- public ushort WinDescent { get; }
-
- public short StrikeoutPosition { get; }
-
- public short StrikeoutSize { get; }
-
- public short SubscriptXOffset { get; }
-
- public short SubscriptXSize { get; }
-
- public short SubscriptYOffset { get; }
-
- public short SubscriptYSize { get; }
-
- public short SuperscriptXOffset { get; }
-
- public short SuperscriptXSize { get; }
-
- public short SuperscriptYOffset { get; }
-
- public short SuperscriptYSize { get; }
-
- public ushort StyleType { get; }
-
- public ushort WeightClass { get; }
-
- public ushort WidthClass { get; }
-
- public static OS2Table? Load(IGlyphTypeface glyphTypeface)
- {
- if (!glyphTypeface.TryGetTable(Tag, out var table))
+ os2Table = default;
+
+ if (!fontFace.PlatformTypeface.TryGetTable(Tag, out var table))
{
- return null;
+ return false;
}
- using var stream = new MemoryStream(table);
- using var binaryReader = new BigEndianBinaryReader(stream, false);
+ var binaryReader = new BigEndianBinaryReader(table.Span);
+
+ os2Table = Load(ref binaryReader);
- // Move to start of table.
- return Load(binaryReader);
+ return true;
}
- public static OS2Table Load(BigEndianBinaryReader reader)
+ private static OS2Table Load(ref BigEndianBinaryReader reader)
{
- // Version 1.0
- // Type | Name | Comments
- // -------|------------------------|-----------------------
- // uint16 |version | 0x0005
- // int16 |xAvgCharWidth |
- // uint16 |usWeightClass |
- // uint16 |usWidthClass |
- // uint16 |fsType |
- // int16 |ySubscriptXSize |
- // int16 |ySubscriptYSize |
- // int16 |ySubscriptXOffset |
- // int16 |ySubscriptYOffset |
- // int16 |ySuperscriptXSize |
- // int16 |ySuperscriptYSize |
- // int16 |ySuperscriptXOffset |
- // int16 |ySuperscriptYOffset |
- // int16 |yStrikeoutSize |
- // int16 |yStrikeoutPosition |
- // int16 |sFamilyClass |
- // uint8 |panose[10] |
- // uint32 |ulUnicodeRange1 | Bits 0–31
- // uint32 |ulUnicodeRange2 | Bits 32–63
- // uint32 |ulUnicodeRange3 | Bits 64–95
- // uint32 |ulUnicodeRange4 | Bits 96–127
- // Tag |achVendID |
- // uint16 |fsSelection |
- // uint16 |usFirstCharIndex |
- // uint16 |usLastCharIndex |
- // int16 |sTypoAscender |
- // int16 |sTypoDescender |
- // int16 |sTypoLineGap |
- // uint16 |usWinAscent |
- // uint16 |usWinDescent |
- // uint32 |ulCodePageRange1 | Bits 0–31
- // uint32 |ulCodePageRange2 | Bits 32–63
- // int16 |sxHeight |
- // int16 |sCapHeight |
- // uint16 |usDefaultChar |
- // uint16 |usBreakChar |
- // uint16 |usMaxContext |
- // uint16 |usLowerOpticalPointSize |
- // uint16 |usUpperOpticalPointSize |
- ushort version = reader.ReadUInt16(); // assert 0x0005
- short averageCharWidth = reader.ReadInt16();
+ ushort version = reader.ReadUInt16();
+ short xAvgCharWidth = reader.ReadInt16();
ushort weightClass = reader.ReadUInt16();
ushort widthClass = reader.ReadUInt16();
- ushort styleType = reader.ReadUInt16();
- short subscriptXSize = reader.ReadInt16();
- short subscriptYSize = reader.ReadInt16();
- short subscriptXOffset = reader.ReadInt16();
- short subscriptYOffset = reader.ReadInt16();
-
- short superscriptXSize = reader.ReadInt16();
- short superscriptYSize = reader.ReadInt16();
- short superscriptXOffset = reader.ReadInt16();
- short superscriptYOffset = reader.ReadInt16();
-
+ ushort fsType = reader.ReadUInt16();
+ short ySubscriptXSize = reader.ReadInt16();
+ short ySubscriptYSize = reader.ReadInt16();
+ short ySubscriptXOffset = reader.ReadInt16();
+ short ySubscriptYOffset = reader.ReadInt16();
+ short ySuperscriptXSize = reader.ReadInt16();
+ short ySuperscriptYSize = reader.ReadInt16();
+ short ySuperscriptXOffset = reader.ReadInt16();
+ short ySuperscriptYOffset = reader.ReadInt16();
short strikeoutSize = reader.ReadInt16();
short strikeoutPosition = reader.ReadInt16();
short familyClass = reader.ReadInt16();
- byte[] panose = reader.ReadUInt8Array(10);
- uint unicodeRange1 = reader.ReadUInt32(); // Bits 0–31
- uint unicodeRange2 = reader.ReadUInt32(); // Bits 32–63
- uint unicodeRange3 = reader.ReadUInt32(); // Bits 64–95
- uint unicodeRange4 = reader.ReadUInt32(); // Bits 96–127
- string tag = reader.ReadTag();
- FontStyleSelection fontStyle = reader.ReadUInt16();
+
+ Panose panose = Panose.Load(ref reader);
+
+ uint unicodeRange1 = reader.ReadUInt32();
+ uint unicodeRange2 = reader.ReadUInt32();
+ uint unicodeRange3 = reader.ReadUInt32();
+ uint unicodeRange4 = reader.ReadUInt32();
+
+ uint vendorId = reader.ReadUInt32();
+
+ FontSelectionFlags selection = reader.ReadUInt16();
ushort firstCharIndex = reader.ReadUInt16();
ushort lastCharIndex = reader.ReadUInt16();
short typoAscender = reader.ReadInt16();
@@ -343,82 +204,75 @@ namespace Avalonia.Media.Fonts.Tables
ushort winAscent = reader.ReadUInt16();
ushort winDescent = reader.ReadUInt16();
- var version0Table = new OS2Table(
- averageCharWidth,
- weightClass,
- widthClass,
- styleType,
- subscriptXSize,
- subscriptYSize,
- subscriptXOffset,
- subscriptYOffset,
- superscriptXSize,
- superscriptYSize,
- superscriptXOffset,
- superscriptYOffset,
- strikeoutSize,
- strikeoutPosition,
- familyClass,
- panose,
- unicodeRange1,
- unicodeRange2,
- unicodeRange3,
- unicodeRange4,
- tag,
- fontStyle,
- firstCharIndex,
- lastCharIndex,
- typoAscender,
- typoDescender,
- typoLineGap,
- winAscent,
- winDescent);
-
- if (version == 0)
- {
- return version0Table;
- }
-
- short heightX = 0;
+ uint codePageRange1 = 0;
+ uint codePageRange2 = 0;
+ short xHeight = 0;
short capHeight = 0;
-
ushort defaultChar = 0;
ushort breakChar = 0;
ushort maxContext = 0;
+ ushort lowerOpticalPointSize = 0;
+ ushort upperOpticalPointSize = 0xFFFF;
- ushort codePageRange1 = reader.ReadUInt16(); // Bits 0–31
- ushort codePageRange2 = reader.ReadUInt16(); // Bits 32–63
+ if (version >= 1)
+ {
+ codePageRange1 = reader.ReadUInt32();
+ codePageRange2 = reader.ReadUInt32();
+ }
- // fields exist only in > v1 https://docs.microsoft.com/en-us/typography/opentype/spec/os2
- if (version > 1)
+ if (version >= 2)
{
- heightX = reader.ReadInt16();
+ xHeight = reader.ReadInt16();
capHeight = reader.ReadInt16();
defaultChar = reader.ReadUInt16();
breakChar = reader.ReadUInt16();
maxContext = reader.ReadUInt16();
}
- var versionLessThan5Table = new OS2Table(
- version0Table,
- codePageRange1,
- codePageRange2,
- heightX,
- capHeight,
- defaultChar,
- breakChar,
- maxContext);
-
- if (version < 5)
+ if (version >= 5)
{
- return versionLessThan5Table;
+ lowerOpticalPointSize = reader.ReadUInt16();
+ upperOpticalPointSize = reader.ReadUInt16();
}
- ushort lowerOpticalPointSize = reader.ReadUInt16();
- ushort upperOpticalPointSize = reader.ReadUInt16();
-
return new OS2Table(
- versionLessThan5Table,
+ version,
+ xAvgCharWidth,
+ weightClass,
+ widthClass,
+ fsType,
+ ySubscriptXSize,
+ ySubscriptYSize,
+ ySubscriptXOffset,
+ ySubscriptYOffset,
+ ySuperscriptXSize,
+ ySuperscriptYSize,
+ ySuperscriptXOffset,
+ ySuperscriptYOffset,
+ strikeoutSize,
+ strikeoutPosition,
+ familyClass,
+ panose,
+ unicodeRange1,
+ unicodeRange2,
+ unicodeRange3,
+ unicodeRange4,
+ vendorId,
+ selection,
+ firstCharIndex,
+ lastCharIndex,
+ typoAscender,
+ typoDescender,
+ typoLineGap,
+ winAscent,
+ winDescent,
+ codePageRange1,
+ codePageRange2,
+ xHeight,
+ capHeight,
+ defaultChar,
+ breakChar,
+ maxContext,
lowerOpticalPointSize,
upperOpticalPointSize);
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Panose.cs b/src/Avalonia.Base/Media/Fonts/Tables/Panose.cs
new file mode 100644
index 0000000000..67d3c7a47d
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Panose.cs
@@ -0,0 +1,248 @@
+namespace Avalonia.Media.Fonts.Tables
+{
+ ///
+ /// Represents the PANOSE classification for a font.
+ /// PANOSE is a font classification system that describes the visual characteristics of a typeface.
+ ///
+ ///
+ /// The interpretation of bytes 1-9 depends on the FamilyKind (byte 0).
+ /// This struct represents the Latin Text interpretation (FamilyKind = 2), which is the most common.
+ /// For other family kinds, access the raw bytes via the indexer.
+ ///
+ internal readonly struct Panose
+ {
+ private readonly byte[] _data;
+
+ public Panose(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8, byte b9)
+ {
+ _data = new byte[10] { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 };
+ }
+
+ public static Panose Load(ref BigEndianBinaryReader reader)
+ {
+ return new Panose(
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte(),
+ reader.ReadByte()
+ );
+ }
+
+ ///
+ /// Gets the family kind classification (byte 0).
+ ///
+ public PanoseFamilyKind FamilyKind => (PanoseFamilyKind)_data[0];
+
+ // Latin Text properties (when FamilyKind == LatinText)
+
+ ///
+ /// Gets the serif style (byte 1) for Latin Text fonts.
+ ///
+ public PanoseSerifStyle SerifStyle => (PanoseSerifStyle)_data[1];
+
+ ///
+ /// Gets the weight (byte 2) for Latin Text fonts.
+ ///
+ public PanoseWeight Weight => (PanoseWeight)_data[2];
+
+ ///
+ /// Gets the proportion (byte 3) for Latin Text fonts.
+ ///
+ public PanoseProportion Proportion => (PanoseProportion)_data[3];
+
+ ///
+ /// Gets the contrast (byte 4) for Latin Text fonts.
+ ///
+ public PanoseContrast Contrast => (PanoseContrast)_data[4];
+
+ ///
+ /// Gets the stroke variation (byte 5) for Latin Text fonts.
+ ///
+ public PanoseStrokeVariation StrokeVariation => (PanoseStrokeVariation)_data[5];
+
+ ///
+ /// Gets the arm style (byte 6) for Latin Text fonts.
+ ///
+ public PanoseArmStyle ArmStyle => (PanoseArmStyle)_data[6];
+
+ ///
+ /// Gets the letterform (byte 7) for Latin Text fonts.
+ ///
+ public PanoseLetterform Letterform => (PanoseLetterform)_data[7];
+
+ ///
+ /// Gets the midline (byte 8) for Latin Text fonts.
+ ///
+ public PanoseMidline Midline => (PanoseMidline)_data[8];
+
+ ///
+ /// Gets the x-height (byte 9) for Latin Text fonts.
+ ///
+ public PanoseXHeight XHeight => (PanoseXHeight)_data[9];
+ }
+
+ internal enum PanoseFamilyKind : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ LatinText = 2,
+ LatinHandWritten = 3,
+ LatinDecorative = 4,
+ LatinSymbol = 5
+ }
+
+ internal enum PanoseSerifStyle : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ Cove = 2,
+ ObtuseCove = 3,
+ SquareCove = 4,
+ ObtuseSquareCove = 5,
+ Square = 6,
+ Thin = 7,
+ Oval = 8,
+ Exaggerated = 9,
+ Triangle = 10,
+ NormalSans = 11,
+ ObtuseSans = 12,
+ PerpendicularSans = 13,
+ Flared = 14,
+ Rounded = 15
+ }
+
+ internal enum PanoseWeight : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ VeryLight = 2,
+ Light = 3,
+ Thin = 4,
+ Book = 5,
+ Medium = 6,
+ Demi = 7,
+ Bold = 8,
+ Heavy = 9,
+ Black = 10,
+ ExtraBlack = 11
+ }
+
+ internal enum PanoseProportion : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ OldStyle = 2,
+ Modern = 3,
+ EvenWidth = 4,
+ Extended = 5,
+ Condensed = 6,
+ VeryExtended = 7,
+ VeryCondensed = 8,
+ Monospaced = 9
+ }
+
+ internal enum PanoseContrast : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ None = 2,
+ VeryLow = 3,
+ Low = 4,
+ MediumLow = 5,
+ Medium = 6,
+ MediumHigh = 7,
+ High = 8,
+ VeryHigh = 9,
+ HorizontalLow = 10,
+ HorizontalMedium = 11,
+ HorizontalHigh = 12,
+ Broken = 13
+ }
+
+ internal enum PanoseStrokeVariation : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ NoVariation = 2,
+ GradualDiagonal = 3,
+ GradualTransitional = 4,
+ GradualVertical = 5,
+ GradualHorizontal = 6,
+ RapidVertical = 7,
+ RapidHorizontal = 8,
+ InstantVertical = 9,
+ InstantHorizontal = 10
+ }
+
+ internal enum PanoseArmStyle : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ StraightArmsHorizontal = 2,
+ StraightArmsWedge = 3,
+ StraightArmsVertical = 4,
+ StraightArmsSingleSerif = 5,
+ StraightArmsDoubleSerif = 6,
+ NonStraightArmsHorizontal = 7,
+ NonStraightArmsWedge = 8,
+ NonStraightArmsVertical = 9,
+ NonStraightArmsSingleSerif = 10,
+ NonStraightArmsDoubleSerif = 11
+ }
+
+ internal enum PanoseLetterform : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ NormalContact = 2,
+ NormalWeighted = 3,
+ NormalBoxed = 4,
+ NormalFlattened = 5,
+ NormalRounded = 6,
+ NormalOffCenter = 7,
+ NormalSquare = 8,
+ ObliqueContact = 9,
+ ObliqueWeighted = 10,
+ ObliqueBoxed = 11,
+ ObliqueFlattened = 12,
+ ObliqueRounded = 13,
+ ObliqueOffCenter = 14,
+ ObliqueSquare = 15
+ }
+
+ internal enum PanoseMidline : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ StandardTrimmed = 2,
+ StandardPointed = 3,
+ StandardSerifed = 4,
+ HighTrimmed = 5,
+ HighPointed = 6,
+ HighSerifed = 7,
+ ConstantTrimmed = 8,
+ ConstantPointed = 9,
+ ConstantSerifed = 10,
+ LowTrimmed = 11,
+ LowPointed = 12,
+ LowSerifed = 13
+ }
+
+ internal enum PanoseXHeight : byte
+ {
+ Any = 0,
+ NoFit = 1,
+ ConstantSmall = 2,
+ ConstantStandard = 3,
+ ConstantLarge = 4,
+ DuckingSmall = 5,
+ DuckingStandard = 6,
+ DuckingLarge = 7
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/PlatformIDs.cs b/src/Avalonia.Base/Media/Fonts/Tables/PlatformID.cs
similarity index 95%
rename from src/Avalonia.Base/Media/Fonts/Tables/PlatformIDs.cs
rename to src/Avalonia.Base/Media/Fonts/Tables/PlatformID.cs
index c57c4e2726..05c864b535 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/PlatformIDs.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/PlatformID.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Media.Fonts.Tables
///
/// platforms ids
///
- internal enum PlatformIDs : ushort
+ internal enum PlatformID : ushort
{
///
/// Unicode platform
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/PostTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/PostTable.cs
new file mode 100644
index 0000000000..a017faa6fc
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/PostTable.cs
@@ -0,0 +1,46 @@
+namespace Avalonia.Media.Fonts.Tables
+{
+ internal readonly struct PostTable
+ {
+ internal const string TableName = "post";
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
+
+ public FontVersion Version { get; }
+ public float ItalicAngle { get; }
+ public short UnderlinePosition { get; }
+ public short UnderlineThickness { get; }
+ public bool IsFixedPitch { get; }
+
+ private PostTable(FontVersion version, float italicAngle, short underlinePosition, short underlineThickness, bool isFixedPitch)
+ {
+ Version = version;
+ ItalicAngle = italicAngle;
+ UnderlinePosition = underlinePosition;
+ UnderlineThickness = underlineThickness;
+ IsFixedPitch = isFixedPitch;
+ }
+
+ public static PostTable Load(GlyphTypeface glyphTypeface)
+ {
+ if (!glyphTypeface.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ return default;
+ }
+
+ var binaryReader = new BigEndianBinaryReader(table.Span);
+
+ return Load(ref binaryReader);
+ }
+
+ private static PostTable Load(ref BigEndianBinaryReader reader)
+ {
+ FontVersion version = reader.ReadVersion16Dot16();
+ float italicAngle = reader.ReadFixed();
+ short underlinePosition = reader.ReadFWORD();
+ short underlineThickness = reader.ReadFWORD();
+ uint isFixedPitch = reader.ReadUInt32();
+
+ return new PostTable(version, italicAngle, underlinePosition, underlineThickness, isFixedPitch != 0);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/StringLoader.cs b/src/Avalonia.Base/Media/Fonts/Tables/StringLoader.cs
deleted file mode 100644
index a42c87b5bd..0000000000
--- a/src/Avalonia.Base/Media/Fonts/Tables/StringLoader.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-// Ported from: https://github.com/SixLabors/Fonts/blob/034a440aece357341fcc6b02db58ffbe153e54ef/src/SixLabors.Fonts
-
-using System.Diagnostics;
-using System.Text;
-
-namespace Avalonia.Media.Fonts.Tables
-{
- [DebuggerDisplay("Offset: {Offset}, Length: {Length}, Value: {Value}")]
- internal class StringLoader
- {
- public StringLoader(ushort length, ushort offset, Encoding encoding)
- {
- Length = length;
- Offset = offset;
- Encoding = encoding;
- Value = string.Empty;
- }
-
- public ushort Length { get; }
-
- public ushort Offset { get; }
-
- public string Value { get; private set; }
-
- public Encoding Encoding { get; }
-
- public static StringLoader Create(BigEndianBinaryReader reader)
- => Create(reader, Encoding.BigEndianUnicode);
-
- public static StringLoader Create(BigEndianBinaryReader reader, Encoding encoding)
- => new StringLoader(reader.ReadUInt16(), reader.ReadUInt16(), encoding);
-
- public void LoadValue(BigEndianBinaryReader reader)
- => Value = reader.ReadString(Length, Encoding).Replace("\0", string.Empty);
- }
-}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/VerticalHeaderTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/VerticalHeaderTable.cs
new file mode 100644
index 0000000000..81ef57d901
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/Tables/VerticalHeaderTable.cs
@@ -0,0 +1,127 @@
+namespace Avalonia.Media.Fonts.Tables
+{
+ internal readonly struct VerticalHeaderTable
+ {
+ internal const string TableName = "vhea";
+ internal static OpenTypeTag Tag { get; } = OpenTypeTag.Parse(TableName);
+
+ public FontVersion Version { get; }
+ public short Ascender { get; }
+ public short Descender { get; }
+ public short LineGap { get; }
+ public ushort AdvanceHeightMax { get; }
+ public short MinTopSideBearing { get; }
+ public short MinBottomSideBearing { get; }
+ public short YMaxExtent { get; }
+ public short CaretSlopeRise { get; }
+ public short CaretSlopeRun { get; }
+ public short CaretOffset { get; }
+ public ushort NumberOfVMetrics { get; }
+
+ public VerticalHeaderTable(
+ FontVersion version,
+ short ascender,
+ short descender,
+ short lineGap,
+ ushort advanceHeightMax,
+ short minTopSideBearing,
+ short minBottomSideBearing,
+ short yMaxExtent,
+ short caretSlopeRise,
+ short caretSlopeRun,
+ short caretOffset,
+ ushort numberOfVMetrics)
+ {
+ Version = version;
+ Ascender = ascender;
+ Descender = descender;
+ LineGap = lineGap;
+ AdvanceHeightMax = advanceHeightMax;
+ MinTopSideBearing = minTopSideBearing;
+ MinBottomSideBearing = minBottomSideBearing;
+ YMaxExtent = yMaxExtent;
+ CaretSlopeRise = caretSlopeRise;
+ CaretSlopeRun = caretSlopeRun;
+ CaretOffset = caretOffset;
+ NumberOfVMetrics = numberOfVMetrics;
+ }
+
+ public static bool TryLoad(GlyphTypeface fontFace, out VerticalHeaderTable verticalHeaderTable)
+ {
+ verticalHeaderTable = default;
+
+ if (!fontFace.PlatformTypeface.TryGetTable(Tag, out var table))
+ {
+ return false;
+ }
+
+ var binaryReader = new BigEndianBinaryReader(table.Span);
+
+ return TryLoad(ref binaryReader, out verticalHeaderTable);
+ }
+
+ private static bool TryLoad(ref BigEndianBinaryReader reader, out VerticalHeaderTable verticalHeaderTable)
+ {
+ verticalHeaderTable = default;
+
+ // See OpenType spec for vhea:
+ // | Version16Dot16 | version | 0x00010000 (1.0) or 0x00011000 (1.1) |
+ // | FWord | ascender | Distance from baseline of highest ascender (vertical) |
+ // | FWord | descender | Distance from baseline of lowest descender (vertical) |
+ // | FWord | lineGap | typographic line gap (vertical) |
+ // | uFWord | advanceHeightMax | must be consistent with vertical metrics |
+ // | FWord | minTopSideBearing | must be consistent with vertical metrics |
+ // | FWord | minBottomSideBearing| must be consistent with vertical metrics |
+ // | FWord | yMaxExtent | max(tsb + (yMax-yMin)) |
+ // | int16 | caretSlopeRise | used to calculate the slope of the caret (rise/run) set to 1 for vertical caret |
+ // | int16 | caretSlopeRun | 0 for vertical |
+ // | FWord | caretOffset | set value to 0 for non-slanted fonts |
+ // | int16 | reserved | set value to 0 |
+ // | int16 | reserved | set value to 0 |
+ // | int16 | reserved | set value to 0 |
+ // | int16 | reserved | set value to 0 |
+ // | int16 | metricDataFormat | 0 for current format |
+ // | uint16 | numOfLongVerMetrics | number of advance heights in vertical metrics table |
+
+ FontVersion version = reader.ReadVersion16Dot16();
+ short ascender = reader.ReadFWORD();
+ short descender = reader.ReadFWORD();
+ short lineGap = reader.ReadFWORD();
+ ushort advanceHeightMax = reader.ReadUFWORD();
+ short minTopSideBearing = reader.ReadFWORD();
+ short minBottomSideBearing = reader.ReadFWORD();
+ short yMaxExtent = reader.ReadFWORD();
+ short caretSlopeRise = reader.ReadInt16();
+ short caretSlopeRun = reader.ReadInt16();
+ short caretOffset = reader.ReadInt16();
+ reader.ReadInt16(); // reserved
+ reader.ReadInt16(); // reserved
+ reader.ReadInt16(); // reserved
+ reader.ReadInt16(); // reserved
+ short metricDataFormat = reader.ReadInt16(); // 0
+
+ if (metricDataFormat != 0)
+ {
+ return false;
+ }
+
+ ushort numberOfVMetrics = reader.ReadUInt16();
+
+ verticalHeaderTable = new VerticalHeaderTable(
+ version,
+ ascender,
+ descender,
+ lineGap,
+ advanceHeightMax,
+ minTopSideBearing,
+ minBottomSideBearing,
+ yMaxExtent,
+ caretSlopeRise,
+ caretSlopeRun,
+ caretOffset,
+ numberOfVMetrics);
+
+ return true;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/UnmanagedFontMemory.cs b/src/Avalonia.Base/Media/Fonts/UnmanagedFontMemory.cs
new file mode 100644
index 0000000000..155f5e5572
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/UnmanagedFontMemory.cs
@@ -0,0 +1,356 @@
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Avalonia.Media.Fonts
+{
+ ///
+ /// Represents a memory manager for unmanaged font data, providing functionality to access and manage font memory
+ /// and OpenType table data.
+ ///
+ /// This class encapsulates unmanaged memory containing font data and provides methods to
+ /// retrieve specific OpenType table data. It ensures thread-safe access to the memory and supports pinning for
+ /// interoperability scenarios. Instances of this class must be properly disposed to release unmanaged
+ /// resources.
+ internal sealed unsafe class UnmanagedFontMemory : MemoryManager, IFontMemory
+ {
+ private IntPtr _ptr;
+ private int _length;
+ private int _pinCount;
+
+ // Reader/writer lock to protect lifetime and cache access.
+ private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
+
+ ///
+ /// Represents a cache of font table data, where each entry maps an OpenType tag to its corresponding byte data.
+ ///
+ /// This dictionary is used to store preloaded font table data for efficient access. The
+ /// keys are OpenType tags, which identify specific font tables, and the values are the corresponding byte data
+ /// stored as read-only memory. This ensures that the data cannot be modified after being loaded into the
+ /// cache.
+ private readonly Dictionary> _tableCache = [];
+
+ private UnmanagedFontMemory(IntPtr ptr, int length)
+ {
+ _ptr = ptr;
+ _length = length;
+ }
+
+ ///
+ /// Attempts to retrieve the memory region corresponding to the specified OpenType table tag.
+ ///
+ /// This method searches for the specified OpenType table in the font data and retrieves
+ /// its memory region if found. The method performs bounds checks to ensure the requested table is valid and
+ /// safely accessible. If the table is not found or the font data is invalid, the method returns .
+ /// The identifying the table to retrieve. Must not be .
+ /// When this method returns, contains the memory region of the requested table if the operation succeeds;
+ /// otherwise, contains the default value.
+ /// if the table memory was successfully retrieved; otherwise, .
+ /// Thrown if the font memory has been disposed.
+ public bool TryGetTable(OpenTypeTag tag, out ReadOnlyMemory table)
+ {
+ table = default;
+
+ // Validate tag
+ if (tag == OpenTypeTag.None)
+ {
+ return false;
+ }
+
+ _lock.EnterUpgradeableReadLock();
+
+ try
+ {
+ if (_ptr == IntPtr.Zero || _length < 12)
+ {
+ return false;
+ }
+
+ // Create a span over the unmanaged memory (read-only view)
+ var fontData = Memory.Span;
+
+ // Minimal SFNT header: 4 (sfnt) + 2 (numTables) + 6 (rest) = 12
+ if (fontData.Length < 12)
+ {
+ return false;
+ }
+
+ // Check cache first
+ if (_tableCache.TryGetValue(tag, out var cached))
+ {
+ table = cached;
+
+ return true;
+ }
+
+ // Parse table directory
+ var numTables = BinaryPrimitives.ReadUInt16BigEndian(fontData.Slice(4, 2));
+ var recordsStart = 12;
+ var requiredDirectoryBytes = checked(recordsStart + numTables * 16);
+
+ if (fontData.Length < requiredDirectoryBytes)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < numTables; i++)
+ {
+ var entryOffset = recordsStart + i * 16;
+ var entrySlice = fontData.Slice(entryOffset, 16);
+ var entryTag = (OpenTypeTag)BinaryPrimitives.ReadUInt32BigEndian(entrySlice.Slice(0, 4));
+
+ if (entryTag != tag)
+ {
+ continue;
+ }
+
+ var offset = BinaryPrimitives.ReadUInt32BigEndian(entrySlice.Slice(8, 4));
+ var length = BinaryPrimitives.ReadUInt32BigEndian(entrySlice.Slice(12, 4));
+
+ // Bounds checks - ensure values fit within the span
+ if (offset > (uint)fontData.Length || length > (uint)fontData.Length)
+ {
+ return false;
+ }
+
+ if (offset + length > (uint)fontData.Length)
+ {
+ return false;
+ }
+
+ // Safe to cast to int for Slice since we validated bounds
+ table = Memory.Slice((int)offset, (int)length);
+
+ // Acquire write lock to update cache
+ _lock.EnterWriteLock();
+
+ try
+ {
+ // Cache the result for faster subsequent lookups
+ _tableCache[tag] = table;
+
+ return true;
+ }
+ finally
+ {
+ // Release write lock
+ _lock.ExitWriteLock();
+ }
+ }
+
+ return false;
+ }
+ finally
+ {
+ // Release upgradeable read lock
+ _lock.ExitUpgradeableReadLock();
+ }
+ }
+
+ ///
+ /// Loads font data from the specified stream into unmanaged memory.
+ ///
+ public static UnmanagedFontMemory LoadFromStream(Stream stream)
+ {
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ if (!stream.CanRead)
+ {
+ throw new ArgumentException("Stream is not readable", nameof(stream));
+ }
+
+ if (stream.CanSeek)
+ {
+ var length = checked((int)stream.Length);
+ var ptr = Marshal.AllocHGlobal(length);
+ var buffer = ArrayPool.Shared.Rent(8192);
+
+ try
+ {
+ var remaining = length;
+ var offset = 0;
+
+ while (remaining > 0)
+ {
+ var toRead = Math.Min(buffer.Length, remaining);
+ var read = stream.Read(buffer, 0, toRead);
+
+ if (read == 0)
+ {
+ break;
+ }
+
+ Marshal.Copy(buffer, 0, ptr + offset, read);
+
+ offset += read;
+
+ remaining -= read;
+ }
+
+ return new UnmanagedFontMemory(ptr, offset);
+ }
+ catch
+ {
+ Marshal.FreeHGlobal(ptr);
+ throw;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
+ else
+ {
+ using var ms = new MemoryStream();
+
+ stream.CopyTo(ms);
+
+ var len = checked((int)ms.Length);
+
+ var buffer = ms.GetBuffer();
+
+ // GetBuffer may return a larger array than the actual data length.
+ return CreateFromBytes(new ReadOnlySpan(buffer, 0, len));
+ }
+ }
+
+ ///
+ /// Creates an instance of from the specified byte data.
+ ///
+ /// The method allocates unmanaged memory to store the provided byte data. The caller is
+ /// responsible for ensuring that the returned instance is properly disposed
+ /// to release the allocated memory.
+ /// A read-only span of bytes representing the font data. The span must not be empty.
+ /// An instance of that encapsulates the unmanaged memory containing the font
+ /// data.
+ private static UnmanagedFontMemory CreateFromBytes(ReadOnlySpan data)
+ {
+ var len = data.Length;
+ var ptr = Marshal.AllocHGlobal(len);
+
+ try
+ {
+ if (len > 0)
+ {
+ unsafe
+ {
+ data.CopyTo(new Span((void*)ptr, len));
+ }
+ }
+
+ return new UnmanagedFontMemory(ptr, len);
+ }
+ catch
+ {
+ Marshal.FreeHGlobal(ptr);
+ throw;
+ }
+ }
+
+ // Implement MemoryManager members on the owner
+ public override Span GetSpan()
+ {
+ _lock.EnterReadLock();
+
+ try
+ {
+ if (_ptr == IntPtr.Zero || _length <= 0)
+ {
+ return Span.Empty;
+ }
+
+ unsafe
+ {
+ return new Span((void*)_ptr.ToPointer(), _length);
+ }
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+
+ public override MemoryHandle Pin(int elementIndex = 0)
+ {
+ if (elementIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elementIndex));
+ }
+
+ // Increment pin count first to prevent dispose racing with pin.
+ Interlocked.Increment(ref _pinCount);
+
+ // Validate state under lock
+ _lock.EnterReadLock();
+
+ try
+ {
+ if (_ptr == IntPtr.Zero || _length == 0)
+ {
+ return new MemoryHandle();
+ }
+
+ if (elementIndex > _length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elementIndex));
+ }
+
+ unsafe
+ {
+ var p = (byte*)_ptr.ToPointer() + elementIndex;
+ return new MemoryHandle(p);
+ }
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+
+ public override void Unpin()
+ {
+ // Decrement pin count
+ Interlocked.Decrement(ref _pinCount);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // Always use lock for disposal since we don't have a finalizer
+ _lock.EnterWriteLock();
+
+ try
+ {
+ if (Volatile.Read(ref _pinCount) > 0)
+ {
+ throw new InvalidOperationException("Cannot dispose while memory is pinned.");
+ }
+
+ if (_ptr != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(_ptr);
+ _ptr = IntPtr.Zero;
+ }
+
+ _length = 0;
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ _lock.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs
index c285ce2f51..5000acb2c5 100644
--- a/src/Avalonia.Base/Media/FormattedText.cs
+++ b/src/Avalonia.Base/Media/FormattedText.cs
@@ -184,13 +184,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
foregroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ runProps.FontFeatures
);
#pragma warning restore 6506
@@ -240,13 +240,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
- fontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ fontFeatures
);
#pragma warning restore 6506
@@ -328,14 +328,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight),
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
- );
+ runProps.CultureInfo,
+ runProps.FontFeatures);
#pragma warning restore 6506
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition,
@@ -388,13 +387,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
- runProps.FontFeatures,
emSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ runProps.FontFeatures
);
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition,
@@ -451,13 +450,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- culture
+ culture,
+ runProps.FontFeatures
);
#pragma warning restore 6506
@@ -511,13 +510,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight),
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ runProps.FontFeatures
);
#pragma warning restore 6506
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
@@ -568,13 +567,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight),
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ runProps.FontFeatures
);
#pragma warning restore 6506
@@ -625,13 +624,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
typeface,
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ runProps.FontFeatures
);
#pragma warning restore 6506
@@ -683,13 +682,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
- runProps.FontFeatures,
runProps.FontRenderingEmSize,
textDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
- runProps.CultureInfo
+ runProps.CultureInfo,
+ runProps.FontFeatures
);
#pragma warning restore 6506
diff --git a/src/Avalonia.Base/Media/GlyphMetrics.cs b/src/Avalonia.Base/Media/GlyphMetrics.cs
index e9b5a112ac..11a4a3fa23 100644
--- a/src/Avalonia.Base/Media/GlyphMetrics.cs
+++ b/src/Avalonia.Base/Media/GlyphMetrics.cs
@@ -10,15 +10,15 @@ public readonly record struct GlyphMetrics
///
/// Distance from the top extremum of the glyph to the y-origin.
///
- public int YBearing{ get; init; }
+ public int YBearing { get; init; }
///
/// Distance from the left extremum of the glyph to the right extremum.
///
- public int Width{ get; init; }
+ public ushort Width { get; init; }
///
/// Distance from the top extremum of the glyph to the bottom extremum.
///
- public int Height{ get; init; }
+ public ushort Height { get; init; }
}
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 489dcb7a40..41eed5b747 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/src/Avalonia.Base/Media/GlyphRun.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Media
/// The baseline origin of the run.
/// The bidi level.
public GlyphRun(
- IGlyphTypeface glyphTypeface,
+ GlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlyMemory characters,
IReadOnlyList glyphIndices,
@@ -61,7 +61,7 @@ namespace Avalonia.Media
/// The baseline origin of the run.
/// The bidi level.
public GlyphRun(
- IGlyphTypeface glyphTypeface,
+ GlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlyMemory characters,
IReadOnlyList glyphInfos,
@@ -90,19 +90,25 @@ namespace Avalonia.Media
}
private static IReadOnlyList CreateGlyphInfos(IReadOnlyList glyphIndices,
- double fontRenderingEmSize, IGlyphTypeface glyphTypeface)
+ double fontRenderingEmSize, GlyphTypeface glyphTypeface)
{
var glyphIndexSpan = ListToSpan(glyphIndices);
- var glyphAdvances = glyphTypeface.GetGlyphAdvances(glyphIndexSpan);
var glyphInfos = new GlyphInfo[glyphIndexSpan.Length];
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
- for (var i = 0; i < glyphIndexSpan.Length; ++i)
+ var advances = glyphIndexSpan.Length <= 256
+ ? stackalloc ushort[glyphIndexSpan.Length]
+ : new ushort[glyphIndexSpan.Length];
+
+ if (glyphTypeface.TryGetHorizontalGlyphAdvances(glyphIndexSpan, advances))
{
- glyphInfos[i] = new GlyphInfo(glyphIndexSpan[i], i, glyphAdvances[i] * scale);
+ for (var i = 0; i < glyphIndexSpan.Length; ++i)
+ {
+ glyphInfos[i] = new GlyphInfo(glyphIndexSpan[i], i, advances[i] * scale);
+ }
}
-
+
return glyphInfos;
}
@@ -137,9 +143,9 @@ namespace Avalonia.Media
}
///
- /// Gets the for the .
+ /// Gets the for the .
///
- public IGlyphTypeface GlyphTypeface { get; }
+ public GlyphTypeface GlyphTypeface { get; }
///
/// Gets or sets the em size used for rendering the .
@@ -205,7 +211,7 @@ namespace Avalonia.Media
}
///
- /// Gets the scale of the current
+ /// Gets the scale of the current
///
internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
@@ -270,7 +276,7 @@ namespace Avalonia.Media
//For in cluster hits we need to move to the start of the next cluster.
if (inClusterHit)
{
- for(; glyphIndex < _glyphInfos.Count; glyphIndex++)
+ for (; glyphIndex < _glyphInfos.Count; glyphIndex++)
{
if (_glyphInfos[glyphIndex].GlyphCluster > characterIndex)
{
@@ -367,7 +373,7 @@ namespace Avalonia.Media
characterIndex = glyphInfo.GlyphCluster;
if (currentX + advance > distance)
- {
+ {
break;
}
diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs
new file mode 100644
index 0000000000..ca8e3fec16
--- /dev/null
+++ b/src/Avalonia.Base/Media/GlyphTypeface.cs
@@ -0,0 +1,655 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using Avalonia.Media.Fonts;
+using Avalonia.Media.Fonts.Tables;
+using Avalonia.Media.Fonts.Tables.Cmap;
+using Avalonia.Media.Fonts.Tables.Metrics;
+using Avalonia.Media.Fonts.Tables.Name;
+using Avalonia.Platform;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Represents a glyph typeface, providing access to font metrics, glyph mappings, and other font-related
+ /// properties.
+ ///
+ /// The class is used to encapsulate font data, including metrics,
+ /// character-to-glyph mappings, and supported OpenType features. It supports platform-specific typefaces and
+ /// applies optional font simulations such as bold or oblique. This class is typically used in text rendering and
+ /// shaping scenarios.
+ public sealed class GlyphTypeface
+ {
+ private static readonly IReadOnlyDictionary s_emptyStringDictionary =
+ new Dictionary(0);
+
+ private bool _isDisposed;
+
+ private readonly NameTable? _nameTable;
+ private readonly OS2Table _os2Table;
+ private readonly CharacterToGlyphMap _cmapTable;
+ private readonly HorizontalHeaderTable _hhTable;
+ private readonly VerticalHeaderTable _vhTable;
+ private readonly HorizontalMetricsTable? _hmTable;
+ private readonly VerticalMetricsTable? _vmTable;
+ private readonly bool _hasOs2Table;
+ private readonly bool _hasHorizontalMetrics;
+ private readonly bool _hasVerticalMetrics;
+
+ private IReadOnlyList? _supportedFeatures;
+ private ITextShaperTypeface? _textShaperTypeface;
+
+ ///
+ /// Initializes a new instance of the class with the specified platform typeface and
+ /// font simulations.
+ ///
+ /// This constructor initializes the glyph typeface by loading various font tables,
+ /// including OS/2, CMAP, and metrics tables, to calculate font metrics and other properties. It also determines
+ /// font characteristics such as weight, style, stretch, and family names based on the provided typeface and
+ /// font simulations.
+ /// The platform-specific typeface to be used for this instance. This parameter
+ /// cannot be null.
+ /// The font simulations to apply, such as bold or oblique. The default is .
+ /// Thrown if required font tables (e.g., 'maxp') cannot be loaded.
+ public GlyphTypeface(IPlatformTypeface typeface, FontSimulations fontSimulations = FontSimulations.None)
+ {
+ PlatformTypeface = typeface;
+
+ _hasOs2Table = OS2Table.TryLoad(this, out _os2Table);
+ _cmapTable = CmapTable.Load(this);
+
+ var maxpTable = MaxpTable.Load(this);
+
+ GlyphCount = maxpTable.NumGlyphs;
+
+ _hasHorizontalMetrics = HorizontalHeaderTable.TryLoad(this, out _hhTable);
+
+ if (_hasHorizontalMetrics)
+ {
+ _hmTable = HorizontalMetricsTable.Load(this, _hhTable.NumberOfHMetrics, GlyphCount);
+ }
+
+ _hasVerticalMetrics = VerticalHeaderTable.TryLoad(this, out _vhTable);
+
+ if (_hasVerticalMetrics)
+ {
+ _vmTable = VerticalMetricsTable.Load(this, _vhTable.NumberOfVMetrics, GlyphCount);
+ }
+
+ var ascent = 0;
+ var descent = 0;
+ var lineGap = 0;
+
+ if (_hasOs2Table && (_os2Table.Selection & OS2Table.FontSelectionFlags.USE_TYPO_METRICS) != 0)
+ {
+ ascent = -_os2Table.TypoAscender;
+ descent = -_os2Table.TypoDescender;
+ lineGap = _os2Table.TypoLineGap;
+ }
+ else
+ {
+ if (_hasHorizontalMetrics)
+ {
+ ascent = -_hhTable.Ascender;
+ descent = -_hhTable.Descender;
+ lineGap = _hhTable.LineGap;
+ }
+ }
+
+ if (_hasOs2Table && (ascent == 0 || descent == 0))
+ {
+ if (_os2Table.TypoAscender != 0 || _os2Table.TypoDescender != 0)
+ {
+ ascent = -_os2Table.TypoAscender;
+ descent = -_os2Table.TypoDescender;
+ lineGap = _os2Table.TypoLineGap;
+ }
+ else
+ {
+ ascent = -_os2Table.WinAscent;
+ descent = _os2Table.WinDescent;
+ }
+ }
+
+ HeadTable.TryLoad(this, out var headTable);
+
+ var postTable = PostTable.Load(this);
+
+ var isFixedPitch = postTable.IsFixedPitch;
+ var underlineOffset = postTable.UnderlinePosition;
+ var underlineSize = postTable.UnderlineThickness;
+
+ Metrics = new FontMetrics
+ {
+ DesignEmHeight = headTable?.UnitsPerEm ?? 0,
+ Ascent = ascent,
+ Descent = descent,
+ LineGap = lineGap,
+ UnderlinePosition = -underlineOffset,
+ UnderlineThickness = underlineSize,
+ StrikethroughPosition = _hasOs2Table ? -_os2Table.StrikeoutPosition : 0,
+ StrikethroughThickness = _hasOs2Table ? _os2Table.StrikeoutSize : 0,
+ IsFixedPitch = isFixedPitch
+ };
+
+ FontSimulations = fontSimulations;
+
+ var fontWeight = GetFontWeight(_hasOs2Table ? _os2Table : null, headTable);
+
+ Weight = (fontSimulations & FontSimulations.Bold) != 0 ? FontWeight.Bold : fontWeight;
+
+ var style = GetFontStyle(_hasOs2Table ? _os2Table : null, headTable, postTable);
+
+ Style = (fontSimulations & FontSimulations.Oblique) != 0 ? FontStyle.Italic : style;
+
+ var stretch = GetFontStretch(_hasOs2Table ? _os2Table : null);
+
+ Stretch = stretch;
+
+ _nameTable = NameTable.Load(this);
+
+ FamilyName = _nameTable?.FontFamilyName((ushort)CultureInfo.InvariantCulture.LCID) ?? "unknown";
+
+ TypographicFamilyName = _nameTable?.GetNameById((ushort)CultureInfo.InvariantCulture.LCID, KnownNameIds.TypographicFamilyName) ?? FamilyName;
+
+ if (_nameTable != null)
+ {
+ Dictionary? familyNames = null;
+ Dictionary? faceNames = null;
+
+ foreach (var nameRecord in _nameTable)
+ {
+ if (nameRecord.NameID == KnownNameIds.FontFamilyName)
+ {
+ if (nameRecord.Platform != Fonts.Tables.PlatformID.Windows || nameRecord.LanguageID == 0)
+ {
+ continue;
+ }
+
+ var culture = GetCulture(nameRecord.LanguageID);
+
+ familyNames ??= new Dictionary(1);
+
+ if (!familyNames.ContainsKey(culture))
+ {
+ familyNames[culture] = nameRecord.GetValue();
+ }
+ }
+
+ if (nameRecord.NameID == KnownNameIds.FontSubfamilyName)
+ {
+ if (nameRecord.Platform != Fonts.Tables.PlatformID.Windows || nameRecord.LanguageID == 0)
+ {
+ continue;
+ }
+
+ var culture = GetCulture(nameRecord.LanguageID);
+
+ faceNames ??= new Dictionary(1);
+
+ if (!faceNames.ContainsKey(culture))
+ {
+ faceNames[culture] = nameRecord.GetValue();
+ }
+ }
+ }
+
+ FamilyNames = familyNames ?? s_emptyStringDictionary;
+ FaceNames = faceNames ?? s_emptyStringDictionary;
+ }
+ else
+ {
+ FamilyNames = new Dictionary { { CultureInfo.InvariantCulture, FamilyName } };
+ FaceNames = new Dictionary { { CultureInfo.InvariantCulture, Weight.ToString() } };
+ }
+
+ static CultureInfo GetCulture(int lcid)
+ {
+ if (lcid == ushort.MaxValue)
+ {
+ return CultureInfo.InvariantCulture;
+ }
+
+ try
+ {
+ return CultureInfo.GetCultureInfo(lcid);
+ }
+ catch (CultureNotFoundException)
+ {
+ return CultureInfo.InvariantCulture;
+ }
+ }
+ }
+
+ ///
+ /// Gets the family name of the font.
+ ///
+ public string FamilyName { get; }
+
+ ///
+ /// Gets the typographic family name of the font.
+ ///
+ public string TypographicFamilyName { get; }
+
+ ///
+ /// Gets a read-only mapping of localized culture-specific family names.
+ ///
+ /// The dictionary contains entries for each supported culture, where the key is a