diff --git a/.gitmodules b/.gitmodules
index 07f532607a..d1463ad26b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "XamlX"]
path = external/XamlX
url = https://github.com/kekekeks/XamlX.git
+[submodule "Avalonia.DBus"]
+ path = external/Avalonia.DBus
+ url = https://github.com/AvaloniaUI/Avalonia.DBus.git
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index b861cf00c5..c3331ebe40 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -8,11 +8,11 @@
"samples\\ControlCatalog\\ControlCatalog.csproj",
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
- "samples\\TextTestApp\\TextTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\RenderDemo\\RenderDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
+ "samples\\TextTestApp\\TextTestApp.csproj",
"samples\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContextPlug\\UnloadableAssemblyLoadContextPlug.csproj",
"samples\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContext.csproj",
"samples\\XEmbedSample\\XEmbedSample.csproj",
@@ -24,18 +24,20 @@
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
+ "src\\Avalonia.FreeDesktop.AtSpi\\Avalonia.FreeDesktop.AtSpi.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
"src\\Avalonia.Metal\\Avalonia.Metal.csproj",
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
- "src\\Avalonia.Vulkan\\Avalonia.Vulkan.csproj",
"src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj",
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
+ "src\\Avalonia.Vulkan\\Avalonia.Vulkan.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.XUnit\\Avalonia.Headless.XUnit.csproj",
"src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
@@ -45,6 +47,7 @@
"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.DBus.Generators\\Avalonia.DBus.Generators.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
@@ -58,6 +61,8 @@
"tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
"tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",
"tests\\Avalonia.Generators.Tests\\Avalonia.Generators.Tests.csproj",
+ "tests\\Avalonia.Headless.XUnit.PerAssembly.UnitTests\\Avalonia.Headless.XUnit.PerAssembly.UnitTests.csproj",
+ "tests\\Avalonia.Headless.XUnit.PerTest.UnitTests\\Avalonia.Headless.XUnit.PerTest.UnitTests.csproj",
"tests\\Avalonia.IntegrationTests.Appium\\Avalonia.IntegrationTests.Appium.csproj",
"tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj",
"tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj",
@@ -69,4 +74,4 @@
"tests\\TestFiles\\BuildTasks\\PInvoke\\PInvoke.csproj"
]
}
-}
+}
\ No newline at end of file
diff --git a/Avalonia.sln b/Avalonia.sln
index 06c3e051ac..4caeb66cf8 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -83,21 +83,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\AnalyzerProject.targets = build\AnalyzerProject.targets
build\AvaloniaPublicKey.props = build\AvaloniaPublicKey.props
build\Base.props = build\Base.props
- build\Binding.props = build\Binding.props
build\CoreLibraries.props = build\CoreLibraries.props
build\DevAnalyzers.props = build\DevAnalyzers.props
build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
- build\ImageSharp.props = build\ImageSharp.props
- build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
- build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
- build\Moq.props = build\Moq.props
build\NetAnalyzers.props = build\NetAnalyzers.props
- build\NetCore.props = build\NetCore.props
- build\NetFX.props = build\NetFX.props
build\NullableEnable.props = build\NullableEnable.props
build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
- build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props
build\SharedVersion.props = build\SharedVersion.props
build\SkiaSharp.props = build\SkiaSharp.props
@@ -113,7 +105,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6F
ProjectSection(SolutionItems) = preProject
build\BuildTargets.targets = build\BuildTargets.targets
build\DevSingleProject.targets = build\DevSingleProject.targets
- build\LegacyProject.targets = build\LegacyProject.targets
build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
EndProjectSection
EndProject
@@ -127,14 +118,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteDemo", "samples\RemoteDemo\RemoteDemo.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}"
@@ -292,6 +279,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Analyzers.CodeFixe
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
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop.AtSpi", "src\Avalonia.FreeDesktop.AtSpi\Avalonia.FreeDesktop.AtSpi.csproj", "{742C3613-514C-4D6B-804A-2A7925F278F3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DBus.Generators", "src\tools\Avalonia.DBus.Generators\Avalonia.DBus.Generators.csproj", "{98A16FFD-0C99-4665-AC64-DC17E86879A2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -412,18 +403,10 @@ Global
{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Any CPU.Build.0 = Release|Any CPU
- {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Any CPU.Build.0 = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.Build.0 = Release|Any CPU
- {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -670,14 +653,22 @@ 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
+ {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
+ {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
+ {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
+ {742C3613-514C-4D6B-804A-2A7925F278F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {742C3613-514C-4D6B-804A-2A7925F278F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {742C3613-514C-4D6B-804A-2A7925F278F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {742C3613-514C-4D6B-804A-2A7925F278F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98A16FFD-0C99-4665-AC64-DC17E86879A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98A16FFD-0C99-4665-AC64-DC17E86879A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98A16FFD-0C99-4665-AC64-DC17E86879A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98A16FFD-0C99-4665-AC64-DC17E86879A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -707,9 +698,7 @@ Global
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
- {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
- {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
@@ -763,8 +752,10 @@ Global
{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}
+ {A7644C3B-B843-44F1-9940-560D56CB0936} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+ {742C3613-514C-4D6B-804A-2A7925F278F3} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
+ {98A16FFD-0C99-4665-AC64-DC17E86879A2} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000000..7e137dbea4
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,76 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NOTICE.md b/NOTICE.md
index 7083706c3e..365f5223fe 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -26,30 +26,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-# SharpDX
-
-https://github.com/sharpdx/SharpDX
-
-Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
# Silverlight Toolkit
https://github.com/microsoftarchive/SilverlightToolkit
diff --git a/NuGet.Config b/NuGet.Config
index 93d7ba8778..a065192b33 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -8,6 +8,12 @@
+
+
+
+
+
+
diff --git a/api/Avalonia.Android.nupkg.xml b/api/Avalonia.Android.nupkg.xml
index 6ffd08efe1..98f8c40204 100644
--- a/api/Avalonia.Android.nupkg.xml
+++ b/api/Avalonia.Android.nupkg.xml
@@ -7,6 +7,18 @@
baseline/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
current/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+
+ CP0002
+ M:Avalonia.Android.AvaloniaMainActivity.CreateAppBuilder
+ baseline/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+ current/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll
+
+
+ CP0002
+ M:Avalonia.Android.AvaloniaMainActivity.CustomizeAppBuilder(Avalonia.AppBuilder)
+ 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
diff --git a/api/Avalonia.Skia.nupkg.xml b/api/Avalonia.Skia.nupkg.xml
index 8e9d60f7d4..cd9dedbd0f 100644
--- a/api/Avalonia.Skia.nupkg.xml
+++ b/api/Avalonia.Skia.nupkg.xml
@@ -25,12 +25,24 @@
baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+
+ CP0002
+ M:Avalonia.Skia.Helpers.DrawingContextHelper.WrapSkiaCanvas(SkiaSharp.SKCanvas,Avalonia.Vector)
+ baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+ current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+
CP0002
M:Avalonia.Skia.ISkiaGpuRenderTarget.BeginRenderingSession
baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll
+
+ CP0002
+ M:Avalonia.Skia.Helpers.DrawingContextHelper.WrapSkiaCanvas(SkiaSharp.SKCanvas,Avalonia.Vector)
+ baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+ current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll
+
CP0002
M:Avalonia.Skia.ISkiaGpuRenderTarget.BeginRenderingSession
diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 838d2bd70b..b729164b3b 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -109,6 +109,42 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Input.IKeyboardNavigationHandler
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.KeyboardNavigationHandler
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.TextInput.ITextInputMethodRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.IEmbeddedLayoutRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.ILayoutRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Media.Fonts.FontFamilyLoader
@@ -151,18 +187,48 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Rendering.IHitTester
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderer
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderRoot
+ 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.MathUtilities
+ 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.VisualTree.IHostedVisualTreeRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetimeOptions
@@ -181,6 +247,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0001
+ T:Avalonia.Controls.Diagnostics.IPopupHostProvider
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0001
T:Avalonia.Controls.FileDialog
@@ -235,12 +307,48 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0001
+ T:Avalonia.Controls.Primitives.ChromeOverlayLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.IPopupHost
+ 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.Primitives.LightDismissOverlayLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.OverlayLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Remote.RemoteServer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Remote.RemoteWidget
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0001
T:Avalonia.Controls.SaveFileDialog
@@ -391,6 +499,42 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Input.IKeyboardNavigationHandler
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.KeyboardNavigationHandler
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.TextInput.ITextInputMethodRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.IEmbeddedLayoutRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.ILayoutRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Media.Fonts.FontFamilyLoader
@@ -433,18 +577,48 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Rendering.IHitTester
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderer
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderRoot
+ 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.MathUtilities
+ 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.VisualTree.IHostedVisualTreeRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetimeOptions
@@ -463,6 +637,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0001
+ T:Avalonia.Controls.Diagnostics.IPopupHostProvider
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0001
T:Avalonia.Controls.FileDialog
@@ -517,12 +697,48 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0001
+ T:Avalonia.Controls.Primitives.ChromeOverlayLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.IPopupHost
+ 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.Primitives.LightDismissOverlayLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.OverlayLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Remote.RemoteServer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Remote.RemoteWidget
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0001
T:Avalonia.Controls.SaveFileDialog
@@ -685,6 +901,42 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_KeyboardNavigationHandler
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PlatformSettings
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PointerOverElement
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_ShowAccessKeys
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_PointerOverElement(Avalonia.Input.IInputElement)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_ShowAccessKeys(System.Boolean)
+ 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)
@@ -775,6 +1027,18 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Layout.LayoutHelper.RoundLayoutSizeUp(Avalonia.Size,System.Double,System.Double)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Layout.LayoutHelper.RoundLayoutThickness(Avalonia.Thickness,System.Double,System.Double)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Media.Color.ToUint32
@@ -973,12 +1237,30 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.#ctor(Avalonia.Rendering.IRenderRoot,Avalonia.Rect)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.get_RenderRoot
+ 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.Visual.get_VisualRoot
+ 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)
@@ -1003,6 +1285,24 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.VisualTree.VisualExtensions.GetVisualRoot(Avalonia.Visual)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.#ctor(Avalonia.Visual,Avalonia.Rendering.IRenderRoot)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.get_Root
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0002
F:Avalonia.Controls.ContextMenu.PlacementModeProperty
@@ -1051,6 +1351,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.Primitives.VisualLayerManager.ChromeOverlayLayerProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
F:Avalonia.Controls.TextBlock.LetterSpacingProperty
@@ -1063,6 +1369,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.TopLevel.PointerOverElementProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.AppBuilder.get_LifetimeOverride
@@ -1081,6 +1393,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Automation.Peers.AutomationPeer.GetVisualRootCore
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.ContextMenu.get_PlacementMode
@@ -1225,18 +1543,54 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.AdornerLayer.#ctor
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.#ctor(Avalonia.Controls.Primitives.OverlayLayer)
+ 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,System.Boolean)
+ 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.OverlayPopupHost.SetChild(Avalonia.Controls.Control)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.TakeFocus
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.Popup.get_Host
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Primitives.Popup.get_PlacementMode
@@ -1321,6 +1675,36 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_AdornerLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_ChromeOverlayLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_OverlayLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_TextSelectorLayer
+ 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)
@@ -1333,6 +1717,30 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.#ctor(Avalonia.Platform.ITopLevelImpl,Avalonia.IAvaloniaDependencyResolver)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.get_PlatformSettings
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StartRendering
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StopRendering
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.TreeView.get_ItemContainerGenerator
@@ -1345,6 +1753,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.WindowBase.ArrangeSetBounds(Avalonia.Size)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Platform.IWindowImpl.GetWindowsZOrder(System.Span{Avalonia.Controls.Window},System.Span{System.Int64})
@@ -1599,7 +2013,43 @@
CP0002
- M:Avalonia.Input.DragEventArgs.get_Data
+ 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.IInputRoot.get_KeyboardNavigationHandler
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PlatformSettings
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PointerOverElement
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_ShowAccessKeys
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_PointerOverElement(Avalonia.Input.IInputElement)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_ShowAccessKeys(System.Boolean)
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
@@ -1693,6 +2143,18 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Layout.LayoutHelper.RoundLayoutSizeUp(Avalonia.Size,System.Double,System.Double)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Layout.LayoutHelper.RoundLayoutThickness(Avalonia.Thickness,System.Double,System.Double)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Media.Color.ToUint32
@@ -1891,12 +2353,30 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.#ctor(Avalonia.Rendering.IRenderRoot,Avalonia.Rect)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.get_RenderRoot
+ 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.Visual.get_VisualRoot
+ 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)
@@ -1921,6 +2401,24 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.VisualTree.VisualExtensions.GetVisualRoot(Avalonia.Visual)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.#ctor(Avalonia.Visual,Avalonia.Rendering.IRenderRoot)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.get_Root
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0002
F:Avalonia.Controls.ContextMenu.PlacementModeProperty
@@ -1969,6 +2467,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.Primitives.VisualLayerManager.ChromeOverlayLayerProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
F:Avalonia.Controls.TextBlock.LetterSpacingProperty
@@ -1981,6 +2485,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.TopLevel.PointerOverElementProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.AppBuilder.get_LifetimeOverride
@@ -1999,6 +2509,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Automation.Peers.AutomationPeer.GetVisualRootCore
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.ContextMenu.get_PlacementMode
@@ -2143,18 +2659,54 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.AdornerLayer.#ctor
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.#ctor(Avalonia.Controls.Primitives.OverlayLayer)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.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/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.CreatePopupHost(Avalonia.Visual,Avalonia.IAvaloniaDependencyResolver,System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
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.Primitives.OverlayPopupHost.SetChild(Avalonia.Controls.Control)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.OverlayPopupHost.TakeFocus
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.Popup.get_Host
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Primitives.Popup.get_PlacementMode
@@ -2239,6 +2791,36 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_AdornerLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_ChromeOverlayLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_OverlayLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_TextSelectorLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)
@@ -2251,6 +2833,30 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.#ctor(Avalonia.Platform.ITopLevelImpl,Avalonia.IAvaloniaDependencyResolver)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.get_PlatformSettings
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StartRendering
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StopRendering
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.TreeView.get_ItemContainerGenerator
@@ -2263,6 +2869,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.WindowBase.ArrangeSetBounds(Avalonia.Size)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Platform.IWindowImpl.GetWindowsZOrder(System.Span{Avalonia.Controls.Window},System.Span{System.Int64})
@@ -2497,6 +3109,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0006
+ P:Avalonia.Input.IInputRoot.FocusRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0006
P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat
@@ -2701,6 +3319,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0006
+ P:Avalonia.Input.IInputRoot.FocusRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0006
P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat
@@ -2839,6 +3463,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0008
+ T:Avalonia.Input.IInputRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0008
T:Avalonia.Media.ImmediateDrawingContext
@@ -2881,6 +3511,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Embedding.EmbeddableControlRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase
@@ -2893,6 +3529,36 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Primitives.OverlayPopupHost
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Primitives.PopupRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.TopLevel
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Window
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.WindowBase
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Platform.IPopupImpl
@@ -2917,6 +3583,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Dialogs.AboutAvaloniaDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+
CP0008
T:Avalonia.Metal.IMetalDevice
@@ -2959,6 +3631,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0008
+ T:Avalonia.Input.IInputRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0008
T:Avalonia.Media.ImmediateDrawingContext
@@ -3001,6 +3679,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Embedding.EmbeddableControlRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase
@@ -3013,6 +3697,36 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Primitives.OverlayPopupHost
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Primitives.PopupRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.TopLevel
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Window
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.WindowBase
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Platform.IPopupImpl
@@ -3037,6 +3751,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Dialogs.AboutAvaloniaDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+
CP0008
T:Avalonia.Metal.IMetalDevice
@@ -3085,6 +3805,24 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0009
+ T:Avalonia.Controls.Primitives.AdornerLayer
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0009
+ T:Avalonia.Controls.Primitives.OverlayPopupHost
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0009
+ T:Avalonia.Controls.Primitives.VisualLayerManager
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0009
T:Avalonia.Platform.Screen
@@ -3103,6 +3841,24 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0009
+ T:Avalonia.Controls.Primitives.AdornerLayer
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0009
+ T:Avalonia.Controls.Primitives.OverlayPopupHost
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0009
+ T:Avalonia.Controls.Primitives.VisualLayerManager
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0009
T:Avalonia.Platform.Screen
diff --git a/build/AnalyzerProject.targets b/build/AnalyzerProject.targets
index 4a95cb306e..5bf69d62d2 100644
--- a/build/AnalyzerProject.targets
+++ b/build/AnalyzerProject.targets
@@ -8,7 +8,7 @@
-
+
diff --git a/build/Base.props b/build/Base.props
index ab5853fcfa..cd0aa8196e 100644
--- a/build/Base.props
+++ b/build/Base.props
@@ -1,8 +1,8 @@
-
-
-
+
+
+
diff --git a/build/Binding.props b/build/Binding.props
deleted file mode 100644
index 9e19e86d89..0000000000
--- a/build/Binding.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index c7a3d753f2..7e0b4e3e19 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/ImageSharp.props b/build/ImageSharp.props
deleted file mode 100644
index cf401630b0..0000000000
--- a/build/ImageSharp.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/build/LegacyProject.targets b/build/LegacyProject.targets
deleted file mode 100644
index 0e0d49b1c2..0000000000
--- a/build/LegacyProject.targets
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/build/Microsoft.CSharp.props b/build/Microsoft.CSharp.props
deleted file mode 100644
index 4f738dd254..0000000000
--- a/build/Microsoft.CSharp.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/build/Microsoft.Reactive.Testing.props b/build/Microsoft.Reactive.Testing.props
deleted file mode 100644
index c39c72df77..0000000000
--- a/build/Microsoft.Reactive.Testing.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/build/Moq.props b/build/Moq.props
deleted file mode 100644
index fc659f7f5f..0000000000
--- a/build/Moq.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/build/NetCore.props b/build/NetCore.props
deleted file mode 100644
index b9cde28015..0000000000
--- a/build/NetCore.props
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/build/NetFX.props b/build/NetFX.props
deleted file mode 100644
index 14adb54035..0000000000
--- a/build/NetFX.props
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/build/Rx.props b/build/Rx.props
deleted file mode 100644
index 462428c286..0000000000
--- a/build/Rx.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/build/SampleApp.props b/build/SampleApp.props
index 3f44553d2d..6816a38cbe 100644
--- a/build/SampleApp.props
+++ b/build/SampleApp.props
@@ -14,7 +14,7 @@
-
+
diff --git a/build/SharpDX.props b/build/SharpDX.props
deleted file mode 100644
index ff521977fd..0000000000
--- a/build/SharpDX.props
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
- 4.0.1
-
-
-
-
-
-
-
-
-
-
-
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 0dc94b3239..c2b04d3026 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/UnitTests.NetFX.props b/build/UnitTests.NetFX.props
index e9a29d80fe..22c40ecbbb 100644
--- a/build/UnitTests.NetFX.props
+++ b/build/UnitTests.NetFX.props
@@ -1,5 +1,4 @@
-
PreserveNewest
diff --git a/build/XUnit.props b/build/XUnit.props
index 5c63ed69db..42b5cae7dc 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -1,7 +1,7 @@
-
+
diff --git a/build/readme.md b/build/readme.md
index e147556b1c..46399914f4 100644
--- a/build/readme.md
+++ b/build/readme.md
@@ -4,12 +4,6 @@
-
-
-
-
-
-
diff --git a/dirs.proj b/dirs.proj
index 084fd30475..20df84439e 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -22,7 +22,4 @@
-
-
-
diff --git a/external/Avalonia.DBus b/external/Avalonia.DBus
new file mode 160000
index 0000000000..f91a822c25
--- /dev/null
+++ b/external/Avalonia.DBus
@@ -0,0 +1 @@
+Subproject commit f91a822c258476f185e51112388775591e6ef9d6
diff --git a/external/XamlX b/external/XamlX
index c32d3040e5..009d481547 160000
--- a/external/XamlX
+++ b/external/XamlX
@@ -1 +1 @@
-Subproject commit c32d3040e536ae9768233ea5a445697632578bd0
+Subproject commit 009d4815470cf4bf71d1adbb633a5d81dcb2bb52
diff --git a/native/Avalonia.Native/src/OSX/StorageProvider.mm b/native/Avalonia.Native/src/OSX/StorageProvider.mm
index 570281fdfd..e0a471a4f3 100644
--- a/native/Avalonia.Native/src/OSX/StorageProvider.mm
+++ b/native/Avalonia.Native/src/OSX/StorageProvider.mm
@@ -148,8 +148,23 @@ public:
[fileUri stopAccessingSecurityScopedResource];
}
}
+
+ static NSWindow* GetEffectiveNSWindow(IAvnTopLevel* topLevel)
+ {
+ auto windowHolder = dynamic_cast(topLevel);
+ if (windowHolder != nullptr)
+ return windowHolder->GetNSWindow();
+
+ auto viewHolder = dynamic_cast(topLevel);
+ if (viewHolder != nullptr) {
+ auto view = (NSView*)viewHolder->GetNSView();
+ return [view window];
+ }
+
+ return nullptr;
+ }
- virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
+ virtual void SelectFolderDialog (IAvnTopLevel* parentTopLevel,
IAvnSystemDialogEvents* events,
bool allowMultiple,
const char* title,
@@ -176,6 +191,8 @@ public:
panel.directoryURL = [NSURL URLWithString:directoryString];
}
+ auto parentWindow = GetEffectiveNSWindow(parentTopLevel);
+
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
@@ -188,10 +205,9 @@ public:
[panel orderOut:panel];
- if(parentWindowHandle != nullptr)
+ if (parentWindow != nullptr)
{
- auto windowHolder = dynamic_cast(parentWindowHandle);
- [windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
+ [parentWindow makeKeyAndOrderFront:parentWindow];
}
return;
@@ -202,11 +218,9 @@ public:
};
- if(parentWindowHandle != nullptr)
+ if (parentWindow != nullptr)
{
- auto windowBase = dynamic_cast(parentWindowHandle);
-
- [panel beginSheetModalForWindow:windowBase->GetNSWindow() completionHandler:handler];
+ [panel beginSheetModalForWindow:parentWindow completionHandler:handler];
}
else
{
@@ -215,7 +229,7 @@ public:
}
}
- virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,
+ virtual void OpenFileDialog (IAvnTopLevel* parentTopLevel,
IAvnSystemDialogEvents* events,
bool allowMultiple,
const char* title,
@@ -249,6 +263,8 @@ public:
panel.directoryURL = [NSURL URLWithString:directoryString];
}
+ auto parentWindow = GetEffectiveNSWindow(parentTopLevel);
+
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
@@ -261,10 +277,9 @@ public:
[panel orderOut:panel];
- if(parentWindowHandle != nullptr)
+ if (parentWindow != nullptr)
{
- auto windowHolder = dynamic_cast(parentWindowHandle);
- [windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
+ [parentWindow makeKeyAndOrderFront:parentWindow];
}
return;
@@ -275,11 +290,9 @@ public:
};
- if(parentWindowHandle != nullptr)
+ if (parentWindow != nullptr)
{
- auto windowHolder = dynamic_cast(parentWindowHandle);
-
- [panel beginSheetModalForWindow:windowHolder->GetNSWindow() completionHandler:handler];
+ [panel beginSheetModalForWindow:parentWindow completionHandler:handler];
}
else
{
@@ -288,7 +301,7 @@ public:
}
}
- virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
+ virtual void SaveFileDialog (IAvnTopLevel* parentTopLevel,
IAvnSystemDialogEvents* events,
const char* title,
const char* initialDirectory,
@@ -319,6 +332,8 @@ public:
panel.directoryURL = [NSURL URLWithString:directoryString];
}
+ auto parentWindow = GetEffectiveNSWindow(parentTopLevel);
+
auto handler = ^(NSModalResponse result) {
int selectedIndex = -1;
if (panel.accessoryView != nil)
@@ -339,10 +354,9 @@ public:
[panel orderOut:panel];
- if(parentWindowHandle != nullptr)
+ if (parentWindow != nullptr)
{
- auto windowHolder = dynamic_cast(parentWindowHandle);
- [windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
+ [parentWindow makeKeyAndOrderFront:parentWindow];
}
return;
@@ -352,11 +366,9 @@ public:
};
- if(parentWindowHandle != nullptr)
+ if (parentWindow != nullptr)
{
- auto windowBase = dynamic_cast(parentWindowHandle);
-
- [panel beginSheetModalForWindow:windowBase->GetNSWindow() completionHandler:handler];
+ [panel beginSheetModalForWindow:parentWindow completionHandler:handler];
}
else
{
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index 5dc994fb6b..092bde9c07 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -1,11 +1,13 @@
#include "common.h"
#include "AvnString.h"
+#include "menu.h"
@interface AvnAppDelegate : NSObject
-(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events;
-(void) releaseEvents;
@end
NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
+static NSMenu* s_dockMenu = nil;
@implementation AvnAppDelegate
ComPtr _events;
@@ -86,6 +88,11 @@ ComPtr _events;
return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel;
}
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender
+{
+ return s_dockMenu;
+}
+
@end
@interface AvnApplication : NSApplication
@@ -180,3 +187,8 @@ extern IAvnApplicationCommands* CreateApplicationCommands()
{
return new AvnApplicationCommands();
}
+
+extern void SetDockMenu(NSMenu* menu)
+{
+ s_dockMenu = menu;
+}
diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm
index f8c288c489..b42dc22f7a 100644
--- a/native/Avalonia.Native/src/OSX/automation.mm
+++ b/native/Avalonia.Native/src/OSX/automation.mm
@@ -220,6 +220,11 @@
return GetNSStringAndRelease(_peer->GetHelpText());
}
+- (NSString *)accessibilityPlaceholderValue
+{
+ return GetNSStringAndRelease(_peer->GetPlaceholderText());
+}
+
- (id)accessibilityValue
{
if (_peer->IsRangeValueProvider())
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index fae03984fd..a993784fc4 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -38,6 +38,7 @@ extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
+extern void SetDockMenu(NSMenu* menu);
extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
extern void ReleaseAvnAppEvents();
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 2a92eb3bcf..2f7e15c8ed 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -1,6 +1,7 @@
//This file will contain actual IID structures
#define COM_GUIDS_MATERIALIZE
#include "common.h"
+#include "menu.h"
static NSString* s_appTitle = @"Avalonia";
static int disableSetProcessName = 0;
@@ -475,14 +476,24 @@ public:
return *ppv != nullptr ? S_OK : E_FAIL;
}
- HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement **ppv) override {
+ HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement **ppv) override {
START_COM_CALL;
*ppv = ::CreateMemoryManagementHelper();
return S_OK;
}
-
-
-
+
+ virtual HRESULT SetDockMenu(IAvnMenu* dockMenu) override
+ {
+ START_COM_CALL;
+
+ @autoreleasepool
+ {
+ auto nativeMenu = dynamic_cast(dockMenu);
+ ::SetDockMenu(nativeMenu != nullptr ? nativeMenu->GetNative() : nil);
+ return S_OK;
+ }
+ }
+
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index c3ee103e73..92f71351c4 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -10,12 +10,12 @@
-
-
+
+
-
-
-
+
+
+
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index e59e72f13b..f86a03133a 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -5,7 +5,7 @@
-
+
@@ -71,7 +71,6 @@
-
diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj
index b12e350814..c38710a5f8 100644
--- a/samples/BindingDemo/BindingDemo.csproj
+++ b/samples/BindingDemo/BindingDemo.csproj
@@ -10,10 +10,11 @@
+
+
+
-
-
diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index c2fe16970a..a630799c05 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -25,7 +25,7 @@
-
+
diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
index c951862ad2..73b983f788 100644
--- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
+++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index 022118d3ab..179f64233e 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -59,6 +59,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index b08df1223d..f14fbb1fa3 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -64,6 +64,40 @@ namespace ControlCatalog
base.OnFrameworkInitializationCompleted();
}
+ public void OnDockNewWindowClicked(object? sender, EventArgs e)
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
+ {
+ var window = new MainWindow();
+ window.Show();
+ }
+ }
+
+ public void OnDockShowMainWindowClicked(object? sender, EventArgs e)
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+ {
+ desktopLifetime.MainWindow?.Activate();
+ }
+ }
+
+ private int _dockMenuItemCount;
+
+ public void OnDockAddItemClicked(object? sender, EventArgs e)
+ {
+ var dockMenu = NativeDock.GetMenu(this);
+ if (dockMenu is not null)
+ {
+ _dockMenuItemCount++;
+ var item = new NativeMenuItem($"New item {_dockMenuItemCount}");
+ item.Click += (_, _) =>
+ {
+ dockMenu.Items.Remove(item);
+ };
+ dockMenu.Items.Insert(0, item);
+ }
+ }
+
private CatalogTheme _prevTheme;
public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
public static void SetCatalogThemes(CatalogTheme theme)
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 803ca52254..20b7d33c60 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -215,7 +215,8 @@
+ SelectionChanged="Decorations_SelectionChanged"
+ ToolTip.Tip="System Decorations">
None
BorderOnly
@@ -225,7 +226,8 @@
+ SelectionChanged="ThemeVariants_SelectionChanged"
+ ToolTip.Tip="Theme Variant">
Default
Light
@@ -234,7 +236,8 @@
+ SelectionChanged="Themes_SelectionChanged"
+ ToolTip.Tip="Catalog Theme">
Fluent
Simple
@@ -242,7 +245,8 @@
+ SelectionChanged="TransparencyLevels_SelectionChanged"
+ ToolTip.Tip="Window Transparency Level">
None
Transparent
@@ -253,7 +257,8 @@
+ SelectionChanged="FlowDirection_SelectionChanged"
+ ToolTip.Tip="Flow Direction">
LeftToRight
RightToLeft
@@ -261,7 +266,8 @@
+ SelectedItem="{Binding WindowState}"
+ ToolTip.Tip="Window State"/>
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index ae8be8cfc6..a6274b56e7 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -45,7 +45,7 @@ namespace ControlCatalog
private void Decorations_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
- if (VisualRoot is Window window && e.AddedItems.Count > 0 && e.AddedItems[0] is SystemDecorations systemDecorations)
+ if (TopLevel.GetTopLevel(this) is Window window && e.AddedItems.Count > 0 && e.AddedItems[0] is SystemDecorations systemDecorations)
{
window.SystemDecorations = systemDecorations;
}
@@ -78,7 +78,7 @@ namespace ControlCatalog
{
base.OnAttachedToVisualTree(e);
- if (VisualRoot is Window window)
+ if (TopLevel.GetTopLevel(this) is Window window)
Decorations.SelectedIndex = (int)window.SystemDecorations;
var insets = TopLevel.GetTopLevel(this)!.InsetsManager;
diff --git a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml
index 7501c80940..e9a245a8e1 100644
--- a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml
+++ b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml
@@ -44,21 +44,31 @@
VerticalContentAlignment="Center" VerticalAlignment="Stretch"
Width="200" Height="42">
-
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs
index c480b512b4..7bc3b96c49 100644
--- a/samples/ControlCatalog/Pages/GesturePage.cs
+++ b/samples/ControlCatalog/Pages/GesturePage.cs
@@ -134,8 +134,8 @@ namespace ControlCatalog.Pages
var currentSize = control.Bounds.Size * _currentScale;
- currentOffset = new Vector3D(MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
- (float)MathUtilities.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
+ currentOffset = new Vector3D(Math.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
+ (float)Math.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
0);
compositionVisual.Offset = currentOffset * -1;
diff --git a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
index f041f32b10..03156f6963 100644
--- a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
@@ -49,7 +49,7 @@ namespace ControlCatalog.ViewModels
public async Task Open()
{
- var window = View?.GetVisualRoot() as Window;
+ var window = TopLevel.GetTopLevel(View) as Window;
if (window == null)
return;
diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
index df62ba04cb..f6b406d0d9 100644
--- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
@@ -69,7 +69,7 @@ namespace ControlCatalog.ViewModels
public async Task Open()
{
- var window = View?.GetVisualRoot() as Window;
+ var window = TopLevel.GetTopLevel(View) as Window;
if (window == null)
return;
var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
diff --git a/samples/Generators.Sandbox/Generators.Sandbox.csproj b/samples/Generators.Sandbox/Generators.Sandbox.csproj
index 5a21bab3e0..2654e1b5b6 100644
--- a/samples/Generators.Sandbox/Generators.Sandbox.csproj
+++ b/samples/Generators.Sandbox/Generators.Sandbox.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/samples/GpuInterop/DrawingSurfaceDemoBase.cs b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
index b076f5b489..10d105bca2 100644
--- a/samples/GpuInterop/DrawingSurfaceDemoBase.cs
+++ b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
@@ -71,12 +71,12 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
void UpdateFrame()
{
_updateQueued = false;
- var root = this.GetVisualRoot();
- if (root == null)
+ var source = this.GetPresentationSource();
+ if (source == null)
return;
_visual!.Size = new (Bounds.Width, Bounds.Height);
- var size = PixelSize.FromSize(Bounds.Size, root.RenderScaling);
+ var size = PixelSize.FromSize(Bounds.Size, source.RenderScaling);
RenderFrame(size);
if (SupportsDisco && Disco > 0)
QueueNextFrame();
diff --git a/samples/GpuInterop/GpuInterop.csproj b/samples/GpuInterop/GpuInterop.csproj
index d952e60524..54428bce72 100644
--- a/samples/GpuInterop/GpuInterop.csproj
+++ b/samples/GpuInterop/GpuInterop.csproj
@@ -27,13 +27,13 @@
-
-
-
-
-
+
+
+
+
+
-
+
diff --git a/samples/IntegrationTestApp/App.axaml b/samples/IntegrationTestApp/App.axaml
index 60a2c56542..e8c91fe580 100644
--- a/samples/IntegrationTestApp/App.axaml
+++ b/samples/IntegrationTestApp/App.axaml
@@ -7,6 +7,13 @@
+
+
+
+
+
(name).IsChecked = true;
});
+ DockMenuCommand = MiniCommand.Create(name =>
+ {
+ // This is for the "Show Main Window" dock menu item in the test.
+ // It doesn't actually show the main window, but sets the checkbox to true in the page.
+ var checkbox = _mainWindow!.GetLogicalDescendants().OfType().FirstOrDefault(x => x.Name == name);
+ if (checkbox != null) checkbox.IsChecked = true;
+ });
DataContext = this;
}
@@ -37,5 +46,21 @@ namespace IntegrationTestApp
}
public ICommand TrayIconCommand { get; }
+ public ICommand DockMenuCommand { get; }
+
+ public void AddDockMenuItem(string header)
+ {
+ var dockMenu = NativeDock.GetMenu(this);
+ if (dockMenu is not null)
+ {
+ dockMenu.Items.Insert(0, new NativeMenuItem(header));
+ }
+ }
+
+ public int GetDockMenuItemCount()
+ {
+ var dockMenu = NativeDock.GetMenu(this);
+ return dockMenu?.Items.Count ?? 0;
+ }
}
}
diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj
index 7e45643843..95caa4c64e 100644
--- a/samples/IntegrationTestApp/IntegrationTestApp.csproj
+++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj
@@ -19,8 +19,8 @@
-
-
+
+
diff --git a/samples/IntegrationTestApp/Pages/AutomationPage.axaml b/samples/IntegrationTestApp/Pages/AutomationPage.axaml
index c02bc1baa6..91317bcb11 100644
--- a/samples/IntegrationTestApp/Pages/AutomationPage.axaml
+++ b/samples/IntegrationTestApp/Pages/AutomationPage.axaml
@@ -10,7 +10,7 @@
TextBlockWithNameAndAutomationId
Label for TextBox
-
+
Foo
diff --git a/samples/IntegrationTestApp/Pages/DesktopPage.axaml b/samples/IntegrationTestApp/Pages/DesktopPage.axaml
index a5495bd347..d7044df525 100644
--- a/samples/IntegrationTestApp/Pages/DesktopPage.axaml
+++ b/samples/IntegrationTestApp/Pages/DesktopPage.axaml
@@ -10,5 +10,11 @@
+
+ Dock Menu Show Main Window Clicked
+
+
diff --git a/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs b/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
index 945727d48e..a5bf3be5fa 100644
--- a/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
+++ b/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
@@ -6,6 +6,8 @@ namespace IntegrationTestApp.Pages;
public partial class DesktopPage : UserControl
{
+ private int _dockMenuItemCount;
+
public DesktopPage()
{
InitializeComponent();
@@ -16,4 +18,12 @@ public partial class DesktopPage : UserControl
var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
icon.IsVisible = !icon.IsVisible;
}
+
+ private void AddDockMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var app = (App)Application.Current!;
+ _dockMenuItemCount++;
+ app.AddDockMenuItem($"Dynamic Item {_dockMenuItemCount}");
+ DockMenuItemCount.Text = app.GetDockMenuItemCount().ToString();
+ }
}
diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj
index faafd6365f..773813617b 100644
--- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj
+++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj
@@ -12,5 +12,7 @@
-
+
+
+
diff --git a/samples/Previewer/App.xaml b/samples/Previewer/App.xaml
deleted file mode 100644
index 817142fefd..0000000000
--- a/samples/Previewer/App.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs
deleted file mode 100644
index ab83d45cd3..0000000000
--- a/samples/Previewer/App.xaml.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Markup.Xaml;
-
-namespace Previewer
-{
- public class App : Application
- {
- public override void Initialize()
- {
- AvaloniaXamlLoader.Load(this);
- }
-
- public override void OnFrameworkInitializationCompleted()
- {
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- desktop.MainWindow = new MainWindow();
- base.OnFrameworkInitializationCompleted();
- }
- }
-
-}
diff --git a/samples/Previewer/Center.cs b/samples/Previewer/Center.cs
deleted file mode 100644
index 7a28827d61..0000000000
--- a/samples/Previewer/Center.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-
-namespace Previewer
-{
- public class Center : Decorator
- {
- protected override Size ArrangeOverride(Size finalSize)
- {
- if (Child != null)
- {
- var desired = Child.DesiredSize;
- Child.Arrange(new Rect((finalSize.Width - desired.Width) / 2, (finalSize.Height - desired.Height) / 2,
- desired.Width, desired.Height));
- }
- return finalSize;
- }
- }
-}
\ No newline at end of file
diff --git a/samples/Previewer/MainWindow.xaml b/samples/Previewer/MainWindow.xaml
deleted file mode 100644
index eb612303f2..0000000000
--- a/samples/Previewer/MainWindow.xaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/Previewer/MainWindow.xaml.cs b/samples/Previewer/MainWindow.xaml.cs
deleted file mode 100644
index dabf90f5d0..0000000000
--- a/samples/Previewer/MainWindow.xaml.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Net;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.Remote;
-using Avalonia.Markup.Xaml;
-using Avalonia.Remote.Protocol;
-using Avalonia.Remote.Protocol.Designer;
-using Avalonia.Remote.Protocol.Viewport;
-using Avalonia.Threading;
-
-namespace Previewer
-{
- public class MainWindow : Window
- {
- private const string InitialXaml = @"
- Hello world!
-
- ";
- private IAvaloniaRemoteTransportConnection? _connection;
- private Control _errorsContainer;
- private TextBlock _errors;
- private RemoteWidget? _remote;
-
-
- public MainWindow()
- {
- this.InitializeComponent();
- var tb = this.GetControl("Xaml");
- tb.Text = InitialXaml;
- var scroll = this.GetControl("Remote");
- var rem = new Center();
- scroll.Content = rem;
- _errorsContainer = this.GetControl("ErrorsContainer");
- _errors = this.GetControl("Errors");
- tb.GetObservable(TextBox.TextProperty).Subscribe(text => _connection?.Send(new UpdateXamlMessage
- {
- Xaml = text
- }));
- new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t =>
- {
- Dispatcher.UIThread.Post(() =>
- {
- if (_connection != null)
- {
- _connection.Dispose();
- _connection.OnMessage -= OnMessage;
- }
- _connection = t;
- rem.Child = _remote = new RemoteWidget(t);
- t.Send(new UpdateXamlMessage
- {
- Xaml = tb.Text
- });
-
- t.OnMessage += OnMessage;
- });
- });
- Title = "Listening on 127.0.0.1:25000";
- }
-
- private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
- {
- Dispatcher.UIThread.Post(() =>
- {
- if (transport != _connection)
- return;
- if (obj is UpdateXamlResultMessage result)
- {
- _errorsContainer.IsVisible = result.Error != null;
- _errors.Text = result.Error ?? "";
- }
- if (obj is RequestViewportResizeMessage resize && _remote is not null)
- {
- _remote.Width = Math.Min(4096, Math.Max(resize.Width, 1));
- _remote.Height = Math.Min(4096, Math.Max(resize.Height, 1));
- }
- });
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
- }
-}
diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj
deleted file mode 100644
index dcc727db8a..0000000000
--- a/samples/Previewer/Previewer.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
- Exe
- $(AvsCurrentTargetFramework)
-
-
-
- %(Filename)
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/Previewer/Program.cs b/samples/Previewer/Program.cs
deleted file mode 100644
index b12b93974a..0000000000
--- a/samples/Previewer/Program.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Avalonia;
-
-namespace Previewer
-{
- class Program
- {
- public static AppBuilder BuildAvaloniaApp()
- => AppBuilder.Configure()
- .UsePlatformDetect();
-
- public static int Main(string[] args)
- => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
- }
-}
diff --git a/samples/RemoteDemo/Program.cs b/samples/RemoteDemo/Program.cs
deleted file mode 100644
index 0565b676fb..0000000000
--- a/samples/RemoteDemo/Program.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.Remote;
-using Avalonia.Remote.Protocol;
-using Avalonia.Threading;
-using ControlCatalog;
-
-namespace RemoteDemo
-{
- class Program
- {
- static void Main(string[] args)
- {
- AppBuilder.Configure().UsePlatformDetect().SetupWithoutStarting();
-
- var l = new TcpListener(IPAddress.Loopback, 0);
- l.Start();
- var port = ((IPEndPoint) l.LocalEndpoint).Port;
- l.Stop();
-
- var transport = new BsonTcpTransport();
- transport.Listen(IPAddress.Loopback, port, sc =>
- {
- Dispatcher.UIThread.Post(() =>
- {
- new RemoteServer(sc).Content = new MainView();
- });
- });
-
- var cts = new CancellationTokenSource();
- transport.Connect(IPAddress.Loopback, port).ContinueWith(t =>
- {
- Dispatcher.UIThread.Post(() =>
- {
- var window = new Window()
- {
- Content = new RemoteWidget(t.Result)
- };
- window.Closed += delegate { cts.Cancel(); };
- window.Show();
- });
- });
- Dispatcher.UIThread.MainLoop(cts.Token);
-
-
-
- }
- }
-}
diff --git a/samples/RemoteDemo/RemoteDemo.csproj b/samples/RemoteDemo/RemoteDemo.csproj
deleted file mode 100644
index 8c479b1093..0000000000
--- a/samples/RemoteDemo/RemoteDemo.csproj
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- Exe
- $(AvsCurrentTargetFramework)
-
-
-
-
-
-
-
-
diff --git a/samples/RenderDemo/App.config b/samples/RenderDemo/App.config
index cd4593817b..86ce1ace90 100644
--- a/samples/RenderDemo/App.config
+++ b/samples/RenderDemo/App.config
@@ -9,14 +9,6 @@
-
-
-
-
-
-
-
-
diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj
index ef6e46dedd..3603ee0d31 100644
--- a/samples/RenderDemo/RenderDemo.csproj
+++ b/samples/RenderDemo/RenderDemo.csproj
@@ -17,10 +17,11 @@
+
+
+
-
-
diff --git a/samples/XEmbedSample/XEmbedSample.csproj b/samples/XEmbedSample/XEmbedSample.csproj
index 2e34bb2cc7..1b51999ada 100644
--- a/samples/XEmbedSample/XEmbedSample.csproj
+++ b/samples/XEmbedSample/XEmbedSample.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/samples/interop/WindowsInteropTest/App.config b/samples/interop/WindowsInteropTest/App.config
deleted file mode 100644
index 4fe7131d2a..0000000000
--- a/samples/interop/WindowsInteropTest/App.config
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index 0c1100ab5f..befdcbcd5e 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs b/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
index ac92aafc29..ca06998dbc 100644
--- a/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
+++ b/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
@@ -135,10 +135,30 @@ namespace Avalonia.Android
protected override bool OnPerformActionForVirtualView(int virtualViewId, int action, Bundle? arguments)
{
return (GetNodeInfoProvidersFromVirtualViewId(virtualViewId) ?? [])
- .Select(x => x.PerformNodeAction(action, arguments))
+ .Select(x => TryPerformNodeAction(x, action, arguments))
.Aggregate(false, (a, b) => a | b);
}
+ private static bool TryPerformNodeAction(INodeInfoProvider nodeInfoProvider, int action, Bundle? arguments)
+ {
+ try
+ {
+ return nodeInfoProvider.PerformNodeAction(action, arguments);
+ }
+ catch (ElementNotEnabledException)
+ {
+ return false;
+ }
+ catch (InvalidOperationException)
+ {
+ return false;
+ }
+ catch (NotSupportedException)
+ {
+ return false;
+ }
+ }
+
protected override void OnPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat? nodeInfo)
{
if (nodeInfo is null || !_peers.TryGetValue(virtualViewId, out AutomationPeer? peer))
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
index 46514cd05f..a5532c3cee 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -44,7 +44,4 @@ public class AvaloniaMainActivity : AvaloniaActivity
activatableLifetime.CurrentMainActivity = null;
}
}
-
- protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure().UseAndroid();
- protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
}
diff --git a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
index 39e1574901..a93803db09 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
+++ b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
@@ -157,8 +157,7 @@ namespace Avalonia.Android.Platform.Input
}
case ImeAction.Next:
{
- FocusManager.GetFocusManager(_toplevel.InputRoot)?
- .TryMoveFocus(NavigationDirection.Next);
+ ((FocusManager?)_toplevel.InputRoot?.FocusManager)?.TryMoveFocus(NavigationDirection.Next);
break;
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 42f0877eca..b11d35d1ef 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -8,7 +8,6 @@ using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using Avalonia.Android.Platform.Input;
-using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
using Avalonia.Controls;
@@ -254,7 +253,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels)
{
- if (_view.Context is not AvaloniaMainActivity activity)
+ if (_view.Context is not AvaloniaActivity activity)
return;
foreach (var level in transparencyLevels)
@@ -366,7 +365,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return false;
}
- private static void SetBlurBehind(AvaloniaMainActivity activity, int radius)
+ private static void SetBlurBehind(AvaloniaActivity activity, int radius)
{
if (radius == 0)
activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind);
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
index d8cd173183..31c4d0c60c 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
@@ -159,7 +159,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
public Task CreateFileAsync(string name)
{
var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream";
- var treeUri = DocumentsContract.BuildDocumentUriUsingTree(Uri, DocumentsContract.GetTreeDocumentId(Uri));
+ var treeUri = GetTreeUri().treeUri;
var newFile = DocumentsContract.CreateDocument(Activity.ContentResolver!, treeUri!, mimeType, name);
if(newFile == null)
{
@@ -171,7 +171,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
public Task CreateFolderAsync(string name)
{
- var treeUri = DocumentsContract.BuildDocumentUriUsingTree(Uri, DocumentsContract.GetTreeDocumentId(Uri));
+ var treeUri = GetTreeUri().treeUri;
var newFolder = DocumentsContract.CreateDocument(Activity.ContentResolver!, treeUri!, DocumentsContract.Document.MimeTypeDir, name);
if (newFolder == null)
{
@@ -207,7 +207,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
}
}
- var treeUri = DocumentsContract.BuildDocumentUriUsingTree(storageFolder.Uri, DocumentsContract.GetTreeDocumentId(storageFolder.Uri));
+ var treeUri = GetTreeUri().treeUri;
DocumentsContract.DeleteDocument(Activity.ContentResolver!, treeUri!);
}
}
@@ -230,9 +230,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
yield break;
}
- var root = PermissionRoot ?? Uri;
- var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri);
- var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId);
+ var (root, childrenUri) = GetTreeUri();
var projection = new[]
{
@@ -311,9 +309,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
return null;
}
- var root = PermissionRoot ?? Uri;
- var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri);
- var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId);
+ var (root, childrenUri) = GetTreeUri();
var projection = new[]
{
@@ -370,6 +366,13 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
var file = await GetItemAsync(name, false);
return (IStorageFile?)file;
}
+
+ private (AndroidUri root, AndroidUri? treeUri) GetTreeUri()
+ {
+ var root = PermissionRoot ?? Uri;
+ var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri);
+ return (root, DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId));
+ }
}
internal sealed class WellKnownAndroidStorageFolder : AndroidStorageFolder
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index ea1dfd766b..ffaac716b9 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -11,7 +11,6 @@
-
@@ -24,6 +23,10 @@
+
+
+
+
diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs
index 758e28e07f..97d08b5620 100644
--- a/src/Avalonia.Base/Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
@@ -30,6 +31,12 @@ namespace Avalonia.Input
RoutingStrategies.Bubble,
typeof(AccessKeyHandler));
+ ///
+ /// Defines the ShowAccessKey attached property.
+ ///
+ public static readonly AttachedProperty ShowAccessKeyProperty =
+ AvaloniaProperty.RegisterAttached("ShowAccessKey", inherits: true);
+
///
/// The registered access keys.
///
@@ -40,7 +47,7 @@ namespace Avalonia.Input
///
/// The window to which the handler belongs.
///
- private IInputRoot? _owner;
+ private InputElement? _owner;
///
/// Whether access keys are currently being shown;
@@ -96,7 +103,7 @@ namespace Avalonia.Input
///
/// This method can only be called once, typically by the owner itself on creation.
///
- public void SetOwner(IInputRoot owner)
+ public void SetOwner(InputElement owner)
{
if (_owner != null)
{
@@ -113,7 +120,7 @@ namespace Avalonia.Input
OnSetOwner(owner);
}
- protected virtual void OnSetOwner(IInputRoot owner)
+ protected virtual void OnSetOwner(InputElement owner)
{
}
@@ -159,6 +166,9 @@ namespace Avalonia.Input
}
}
+ static void SetShowAccessKeys(AvaloniaObject target, bool value) =>
+ target.SetValue(ShowAccessKeyProperty, value);
+
///
/// Called when a key is pressed in the owner window.
///
@@ -188,7 +198,7 @@ namespace Avalonia.Input
// When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File").
- _owner!.ShowAccessKeys = _showingAccessKeys = isFocusWithinOwner;
+ SetShowAccessKeys(_owner!, _showingAccessKeys = isFocusWithinOwner);
}
else
{
@@ -265,7 +275,7 @@ namespace Avalonia.Input
{
if (_showingAccessKeys)
{
- _owner!.ShowAccessKeys = false;
+ SetShowAccessKeys(_owner!, false);
}
}
@@ -275,12 +285,12 @@ namespace Avalonia.Input
private void CloseMenu()
{
MainMenu!.Close();
- _owner!.ShowAccessKeys = _showingAccessKeys = false;
+ SetShowAccessKeys(_owner!, _showingAccessKeys = false);
}
private void MainMenuClosed(object? sender, EventArgs e)
{
- _owner!.ShowAccessKeys = false;
+ SetShowAccessKeys(_owner!, false);
}
///
@@ -444,7 +454,7 @@ namespace Avalonia.Input
///
/// The owner to check.
/// If focused element is decendant of owner true, otherwise false.
- private static bool IsFocusWithinOwner(IInputRoot owner)
+ private static bool IsFocusWithinOwner(IInputElement owner)
{
var focusedElement = KeyboardDevice.Instance?.FocusedElement;
if (focusedElement is not InputElement inputElement)
diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs
index 8f3b542e8d..21321510e6 100644
--- a/src/Avalonia.Base/Input/DragDropDevice.cs
+++ b/src/Avalonia.Base/Input/DragDropDevice.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Input
private static Interactive? GetTarget(IInputRoot root, Point local)
{
- var hit = root.InputHitTest(local) as Visual;
+ var hit = root.RootElement?.InputHitTest(local) as Visual;
var target = hit?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target))
return target;
@@ -35,7 +35,7 @@ namespace Avalonia.Input
if (target == null)
return DragDropEffects.None;
- var p = ((Visual)inputRoot).TranslatePoint(point, target);
+ var p = (inputRoot.RootElement).TranslatePoint(point, target);
if (!p.HasValue)
return DragDropEffects.None;
diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs
index ed741a36ae..15b8fea77d 100644
--- a/src/Avalonia.Base/Input/FocusManager.cs
+++ b/src/Avalonia.Base/Input/FocusManager.cs
@@ -51,6 +51,11 @@ namespace Avalonia.Input
{
_contentRoot = contentRoot;
}
+
+ internal void SetContentRoot(IInputElement? contentRoot)
+ {
+ _contentRoot = contentRoot;
+ }
private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
@@ -120,7 +125,7 @@ namespace Avalonia.Input
scope.ClearValue(FocusedElementProperty);
}
- if (Current == removedElement)
+ if (Current == removedElement)
Focus(null);
}
@@ -163,9 +168,10 @@ namespace Avalonia.Input
///
internal static FocusManager? GetFocusManager(IInputElement? element)
{
+
// Element might not be a visual, and not attached to the root.
// But IFocusManager is always expected to be a FocusManager.
- return (FocusManager?)((element as Visual)?.VisualRoot as IInputRoot)?.FocusManager
+ return (FocusManager?)(element as Visual)?.GetInputRoot()?.FocusManager
// In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService();
}
@@ -240,10 +246,10 @@ namespace Avalonia.Input
if (scope is not Visual v)
return null;
- var root = v.VisualRoot as Visual;
+ var root = v.PresentationSource?.InputRoot.FocusRoot as Visual;
while (root is IHostedVisualTreeRoot hosted &&
- hosted.Host?.VisualRoot is Visual parentRoot)
+ hosted.Host?.PresentationSource?.InputRoot.FocusRoot is {} parentRoot)
{
root = parentRoot;
}
diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs
index 3298af3a0f..4929cd0ea6 100644
--- a/src/Avalonia.Base/Input/Gestures.cs
+++ b/src/Avalonia.Base/Input/Gestures.cs
@@ -257,7 +257,7 @@ namespace Avalonia.Input
s_lastPressPoint = e.GetPosition((Visual)source);
s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token;
- var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings;
+ var settings = visual.GetPlatformSettings();
if (settings != null)
{
@@ -298,7 +298,7 @@ namespace Avalonia.Input
source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
- var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
+ var settings = i.GetPlatformSettings();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
@@ -340,7 +340,6 @@ namespace Avalonia.Input
if (e.Pointer == s_gestureState?.Pointer && source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
- var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var holdSize = new Size(4, 4);
var holdRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(holdSize.Width, holdSize.Height));
diff --git a/src/Avalonia.Base/Input/IAccessKeyHandler.cs b/src/Avalonia.Base/Input/IAccessKeyHandler.cs
index 418fa61f05..44dda96eb1 100644
--- a/src/Avalonia.Base/Input/IAccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/IAccessKeyHandler.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Input
///
/// This method can only be called once, typically by the owner itself on creation.
///
- void SetOwner(IInputRoot owner);
+ void SetOwner(InputElement owner);
///
/// Registers an input element to be associated with an access key.
diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs
index c1c5968ebe..513ecb0fae 100644
--- a/src/Avalonia.Base/Input/IInputRoot.cs
+++ b/src/Avalonia.Base/Input/IInputRoot.cs
@@ -1,3 +1,4 @@
+using Avalonia.Input.TextInput;
using Avalonia.Metadata;
using Avalonia.Platform;
@@ -6,38 +7,31 @@ namespace Avalonia.Input
///
/// Defines the interface for top-level input elements.
///
- [NotClientImplementable]
- public interface IInputRoot : IInputElement
+ [PrivateApi]
+ public interface IInputRoot
{
- ///
- /// Gets or sets the keyboard navigation handler.
- ///
- IKeyboardNavigationHandler? KeyboardNavigationHandler { get; }
-
///
/// Gets focus manager of the root.
///
///
/// Focus manager can be null only if window wasn't initialized yet.
///
- IFocusManager? FocusManager { get; }
-
- ///
- /// Represents a contract for accessing top-level platform-specific settings.
- ///
- ///
- /// PlatformSettings can be null only if window wasn't initialized yet.
- ///
- IPlatformSettings? PlatformSettings { get; }
+ public IFocusManager? FocusManager { get; }
///
/// Gets or sets the input element that the pointer is currently over.
///
- IInputElement? PointerOverElement { get; set; }
-
- ///
- /// Gets or sets a value indicating whether access keys are shown in the window.
- ///
- bool ShowAccessKeys { get; set; }
+ internal IInputElement? PointerOverElement { get; set; }
+
+ internal ITextInputMethodImpl? InputMethod { get; }
+
+ internal InputElement RootElement { get; }
+
+ // HACK: This is a temporary hack for "default focus" concept.
+ // If nothing is focused we send keyboard events to Window. Since for now we always
+ // control PresentationSource, we simply pass the TopLevel as a separate parameter there.
+ // It's also currently used by automation since we have special WindowAutomationPeer which needs to target the
+ // window itself
+ public InputElement FocusRoot { get; }
}
}
diff --git a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
index e82bb5d216..5e5cac0c0b 100644
--- a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
+++ b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Input
/// Defines the interface for classes that handle keyboard navigation for a window.
///
[Unstable]
- public interface IKeyboardNavigationHandler
+ internal interface IKeyboardNavigationHandler
{
///
/// Sets the owner of the keyboard navigation handler.
@@ -16,7 +16,7 @@ namespace Avalonia.Input
/// This method can only be called once, typically by the owner itself on creation.
///
[PrivateApi]
- void SetOwner(IInputRoot owner);
+ void SetOwner(InputElement owner);
///
/// Moves the focus in the specified direction.
diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs
index d15abfbd3f..f929dcbe05 100644
--- a/src/Avalonia.Base/Input/InputElement.cs
+++ b/src/Avalonia.Base/Input/InputElement.cs
@@ -583,7 +583,9 @@ namespace Avalonia.Input
if (IsFocused)
{
- FocusManager.GetFocusManager(e.Root as IInputElement)?.ClearFocusOnElementRemoved(this, e.Parent);
+ var root = e.AttachmentPoint ?? e.RootVisual;
+ ((FocusManager?)e.PresentationSource.InputRoot.FocusManager)
+ ?.ClearFocusOnElementRemoved(this, root);
}
IsKeyboardFocusWithin = false;
diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs
index 3971ef9364..3d9764528a 100644
--- a/src/Avalonia.Base/Input/KeyboardDevice.cs
+++ b/src/Avalonia.Base/Input/KeyboardDevice.cs
@@ -191,15 +191,15 @@ namespace Avalonia.Input
// Clear keyboard focus from currently focused element
if (FocusedElement != null &&
(!((Visual)FocusedElement).IsAttachedToVisualTree ||
- _focusedRoot != ((Visual?)element)?.VisualRoot as IInputRoot) &&
+ _focusedRoot != ((Visual?)element)?.GetInputRoot()) &&
_focusedRoot != null)
{
- ClearChildrenFocusWithin(_focusedRoot, true);
+ ClearChildrenFocusWithin(_focusedRoot.RootElement, true);
}
SetIsFocusWithin(FocusedElement, element);
_focusedElement = element;
- _focusedRoot = ((Visual?)_focusedElement)?.VisualRoot as IInputRoot;
+ _focusedRoot = (_focusedElement as Visual)?.GetInputRoot();
interactive?.RaiseEvent(new RoutedEventArgs(InputElement.LostFocusEvent));
@@ -225,7 +225,7 @@ namespace Avalonia.Input
if(e.Handled)
return;
- var element = FocusedElement ?? e.Root;
+ var element = FocusedElement ?? e.Root.FocusRoot;
if (e is RawKeyEventArgs keyInput)
{
diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
index e5e7eb0699..88a293f6bb 100644
--- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
+++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
@@ -10,13 +10,12 @@ namespace Avalonia.Input
///
/// Handles keyboard navigation for a window.
///
- [Unstable]
- public sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
+ internal sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
{
///
/// The window to which the handler belongs.
///
- private IInputRoot? _owner;
+ private InputElement? _owner;
///
/// Sets the owner of the keyboard navigation handler.
@@ -26,7 +25,7 @@ namespace Avalonia.Input
/// This method can only be called once, typically by the owner itself on creation.
///
[PrivateApi]
- public void SetOwner(IInputRoot owner)
+ public void SetOwner(InputElement owner)
{
if (_owner != null)
{
@@ -56,7 +55,7 @@ namespace Avalonia.Input
private static IInputElement? GetNextPrivate(
IInputElement? element,
- IInputRoot? owner,
+ InputElement? owner,
NavigationDirection direction,
KeyDeviceType? keyDeviceType)
{
diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs
index 62105c7deb..d9766c1707 100644
--- a/src/Avalonia.Base/Input/MouseDevice.cs
+++ b/src/Avalonia.Base/Input/MouseDevice.cs
@@ -135,20 +135,20 @@ namespace Avalonia.Input
return new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind());
}
- private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
+ private bool MouseDown(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var source = _pointer.Captured ?? root.InputHitTest(p);
+ var source = _pointer.Captured ?? root.RootElement.InputHitTest(p);
if (source != null)
{
_pointer.Capture(source, CaptureSource.Implicit);
- var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ var settings = (source as Interactive)?.GetPlatformSettings();
if (settings is not null)
{
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
@@ -166,7 +166,7 @@ namespace Avalonia.Input
}
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
- var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
+ var e = new PointerPressedEventArgs(source, _pointer, root.RootElement, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
}
@@ -185,7 +185,7 @@ namespace Avalonia.Input
if (source is object)
{
- var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root.RootElement,
p, timestamp, properties, inputModifiers, intermediatePoints);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
@@ -209,7 +209,7 @@ namespace Avalonia.Input
if (source is not null)
{
- var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
+ var e = new PointerReleasedEventArgs(source, _pointer, root.RootElement, p, timestamp, props, inputModifiers,
_lastMouseDownButton);
try
@@ -244,7 +244,7 @@ namespace Avalonia.Input
if (source is not null)
{
- var e = new PointerWheelEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ var e = new PointerWheelEventArgs(source, _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -264,7 +264,7 @@ namespace Avalonia.Input
if (source != null)
{
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source,
- _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -284,7 +284,7 @@ namespace Avalonia.Input
if (source != null)
{
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source,
- _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -304,7 +304,7 @@ namespace Avalonia.Input
if (source != null)
{
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source,
- _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
index 0f529142ca..ed93b86475 100644
--- a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
+++ b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
@@ -105,12 +105,11 @@ public partial class XYFocus
private static bool IsOccluded(InputElement element, Rect elementBounds)
{
- // if (element is CHyperlink hyperlink)
- // {
- // element = hyperlink.GetContainingFrameworkElement();
- // }
-
- var root = (InputElement)element.GetVisualRoot()!;
+ // TODO: The check for bounds is no longer correct
+
+ var root = (InputElement?)element.VisualRoot;
+ if (root == null)
+ return true;
// Check if the element is within the visible area of the window
var visibleBounds = new Rect(0, 0, root.Bounds.Width, root.Bounds.Height);
diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
index 929d92a650..7aab6d8b65 100644
--- a/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
+++ b/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
@@ -117,7 +117,9 @@ public partial class XYFocus
{
if (element == null) return null;
- var root = (InputElement)element.GetVisualRoot()!;
+ var root = (InputElement?)element.VisualRoot;
+ if (root == null)
+ return null;
var isRightToLeft = element.FlowDirection == FlowDirection.RightToLeft;
var mode = GetStrategy(element, direction, xyFocusOptions.NavigationStrategyOverride);
diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs
index 3f168f66b0..b3301ce612 100644
--- a/src/Avalonia.Base/Input/PenDevice.cs
+++ b/src/Avalonia.Base/Input/PenDevice.cs
@@ -97,7 +97,7 @@ namespace Avalonia.Input
}
private bool PenDown(Pointer pointer, ulong timestamp,
- IInputElement root, Point p, PointerPointProperties properties,
+ IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
var source = pointer.Captured ?? hitTest;
@@ -105,7 +105,7 @@ namespace Avalonia.Input
if (source != null)
{
pointer.Capture(source);
- var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ var settings = (source as Interactive)?.GetPlatformSettings();
if (settings is not null)
{
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds;
@@ -123,7 +123,7 @@ namespace Avalonia.Input
}
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
- var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
+ var e = new PointerPressedEventArgs(source, pointer, root.RootElement, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
}
@@ -140,7 +140,7 @@ namespace Avalonia.Input
if (source is not null)
{
- var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, root.RootElement,
p, timestamp, properties, inputModifiers, intermediatePoints);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
@@ -154,14 +154,14 @@ namespace Avalonia.Input
}
private bool PenUp(Pointer pointer, ulong timestamp,
- IInputElement root, Point p, PointerPointProperties properties,
+ IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
-
+
if (source is not null)
{
- var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
+ var e = new PointerReleasedEventArgs(source, pointer, root.RootElement, p, timestamp, properties, inputModifiers,
_lastMouseDownButton);
try
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index ebb98dd0bc..7de3dbe713 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -108,14 +108,14 @@ namespace Avalonia.Input
}
}
- static IInputElement? GetNextCapture(Visual parent)
+ static IInputElement? GetNextCapture(Visual? parent)
{
return parent as IInputElement ?? parent.FindAncestorOfType();
}
private void OnCaptureDetached(object? sender, VisualTreeAttachmentEventArgs e)
{
- Capture(GetNextCapture(e.Parent));
+ Capture(GetNextCapture(e.AttachmentPoint));
}
diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
index ed2b4c8669..beb171467d 100644
--- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
+++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
@@ -38,7 +38,7 @@ namespace Avalonia.Input
// occurred.
//
// Solve this by updating the last known pointer position when a drag event occurs.
- _lastKnownPosition = ((Visual)_inputRoot).PointToScreen(dragArgs.Location);
+ _lastKnownPosition = _inputRoot.RootElement.PointToScreen(dragArgs.Location);
}
else if (value is RawPointerEventArgs args
@@ -64,7 +64,7 @@ namespace Avalonia.Input
args.InputModifiers.ToKeyModifiers());
}
}
- else if (args.Type is RawPointerEventType.TouchBegin or RawPointerEventType.TouchUpdate && args.Root is Visual visual)
+ else if (args.Type is RawPointerEventType.TouchBegin or RawPointerEventType.TouchUpdate && args.Root.RootElement is {} visual)
{
_lastKnownPosition = visual.PointToScreen(args.Position);
}
@@ -99,12 +99,12 @@ namespace Avalonia.Input
if (dirtyRect.Contains(clientPoint))
{
var element = GetEffectivePointerOverElement(
- _inputRoot.InputHitTest(clientPoint),
+ _inputRoot.RootElement.InputHitTest(clientPoint),
pointer.Captured);
SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
- else if (!((Visual)_inputRoot).Bounds.Contains(clientPoint))
+ else if (!_inputRoot.RootElement.Bounds.Contains(clientPoint))
{
ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
@@ -140,16 +140,16 @@ namespace Avalonia.Input
// so GetPosition won't return invalid values.
#pragma warning disable CS0618
var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer,
- position.HasValue ? root as Visual : null, position.HasValue ? position.Value : default,
+ position.HasValue ? root.RootElement : null, position.HasValue ? position.Value : default,
timestamp, properties, inputModifiers);
#pragma warning restore CS0618
if (element is Visual v && !v.IsAttachedToVisualTree)
{
// element has been removed from visual tree so do top down cleanup
- if (root.IsPointerOver)
+ if (root.RootElement.IsPointerOver)
{
- ClearChildrenPointerOver(e, root, true);
+ ClearChildrenPointerOver(e, root.RootElement, true);
}
}
while (element != null)
@@ -191,7 +191,7 @@ namespace Avalonia.Input
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
{
var pointerOverElement = root.PointerOverElement;
- var screenPosition = ((Visual)root).PointToScreen(position);
+ var screenPosition = (root.RootElement).PointToScreen(position);
_lastKnownPosition = screenPosition;
if (element != pointerOverElement)
@@ -229,7 +229,7 @@ namespace Avalonia.Input
el = root.PointerOverElement;
#pragma warning disable CS0618
- var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, (Visual)root, position,
+ var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, root.RootElement, position,
timestamp, properties, inputModifiers);
#pragma warning restore CS0618
if (el is Visual v && branch != null && !v.IsAttachedToVisualTree)
@@ -265,7 +265,7 @@ namespace Avalonia.Input
private static Point PointToClient(IInputRoot root, PixelPoint p)
{
- return ((Visual)root).PointToClient(p);
+ return (root.RootElement).PointToClient(p);
}
}
}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
index 2969b2e60d..471bbaa081 100644
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
+++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
@@ -10,10 +10,4 @@ namespace Avalonia.Input.TextInput
void SetOptions(TextInputOptions options);
void Reset();
}
-
- [NotClientImplementable]
- public interface ITextInputMethodRoot : IInputRoot
- {
- ITextInputMethodImpl? InputMethod { get; }
- }
}
diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
index ae3a8bc6a1..930e827874 100644
--- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
+++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Input.TextInput
private IInputElement? _focusedElement;
private Interactive? _visualRoot;
private TextInputMethodClient? _client;
- private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
+ private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper(true);
public TextInputMethodManager()
{
@@ -132,7 +132,7 @@ namespace Avalonia.Input.TextInput
InputMethod.AddTextInputMethodClientRequeryRequestedHandler(_visualRoot,
TextInputMethodClientRequeryRequested);
- var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
+ var inputMethod = ((element as Visual)?.GetInputRoot())?.InputMethod;
if (_im != inputMethod)
{
diff --git a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
index 1ea754f0f4..c5954bc600 100644
--- a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
+++ b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -7,13 +9,15 @@ namespace Avalonia.Input.TextInput
{
class TransformTrackingHelper : IDisposable
{
+ private readonly bool _deferAfterRenderPass;
private Visual? _visual;
private bool _queuedForUpdate;
private readonly EventHandler _propertyChangedHandler;
private readonly List _propertyChangedSubscriptions = new List();
- public TransformTrackingHelper()
+ public TransformTrackingHelper(bool deferAfterRenderPass)
{
+ _deferAfterRenderPass = deferAfterRenderPass;
_propertyChangedHandler = PropertyChangedHandler;
}
@@ -24,7 +28,7 @@ namespace Avalonia.Input.TextInput
if (visual != null)
{
visual.AttachedToVisualTree += OnAttachedToVisualTree;
- visual.DetachedFromVisualTree -= OnDetachedFromVisualTree;
+ visual.DetachedFromVisualTree += OnDetachedFromVisualTree;
if (visual.IsAttachedToVisualTree)
SubscribeToParents();
UpdateMatrix();
@@ -70,6 +74,7 @@ namespace Avalonia.Input.TextInput
void UpdateMatrix()
{
+ _queuedForUpdate = false;
Matrix? matrix = null;
if (_visual != null && _visual.VisualRoot != null)
matrix = _visual.TransformToVisual((Visual)_visual.VisualRoot);
@@ -91,7 +96,10 @@ namespace Avalonia.Input.TextInput
if(_queuedForUpdate)
return;
_queuedForUpdate = true;
- Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.AfterRender);
+ if (_deferAfterRenderPass)
+ Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.AfterRender);
+ else
+ MediaContext.Instance.BeginInvokeOnRender(UpdateMatrix);
}
private void PropertyChangedHandler(object? sender, AvaloniaPropertyChangedEventArgs e)
@@ -106,12 +114,23 @@ namespace Avalonia.Input.TextInput
UpdateMatrix();
}
- public static IDisposable Track(Visual visual, Action cb)
+ public static IDisposable Track(Visual visual, bool deferAfterRenderPass, Action cb)
{
- var rv = new TransformTrackingHelper();
+ var rv = new TransformTrackingHelper(deferAfterRenderPass);
rv.MatrixChanged += () => cb(visual, rv.Matrix);
rv.SetVisual(visual);
return rv;
}
+
+ public static IObservable Observe(Visual visual, bool deferAfterRenderPass)
+ {
+ return Observable.Create(observer =>
+ {
+ var rv = new TransformTrackingHelper(deferAfterRenderPass);
+ rv.MatrixChanged += () => observer.OnNext(rv.Matrix);
+ rv.SetVisual(visual);
+ return rv;
+ });
+ }
}
}
diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs
index 8e662ea1b9..27381bbe00 100644
--- a/src/Avalonia.Base/Input/TouchDevice.cs
+++ b/src/Avalonia.Base/Input/TouchDevice.cs
@@ -51,7 +51,7 @@ namespace Avalonia.Input
pointer.Capture(hit);
}
- var target = pointer.Captured ?? args.InputHitTestResult.firstEnabledAncestor ?? args.Root;
+ var target = pointer.Captured ?? args.InputHitTestResult.firstEnabledAncestor ?? args.Root.RootElement;
var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers();
@@ -66,7 +66,7 @@ namespace Avalonia.Input
}
else
{
- var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ var settings = (target as Interactive)?.GetPlatformSettings();
if (settings is not null)
{
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
@@ -86,7 +86,7 @@ namespace Avalonia.Input
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
- (Visual)args.Root, args.Position, ev.Timestamp,
+ args.Root.RootElement, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point),
keyModifier, _clickCount));
}
@@ -98,7 +98,7 @@ namespace Avalonia.Input
{
target = gestureTarget ?? target;
var e = new PointerReleasedEventArgs(target, pointer,
- (Visual)args.Root, args.Position, ev.Timestamp,
+ args.Root.RootElement, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind, args.Point),
keyModifier, MouseButton.Left);
if (gestureTarget != null)
@@ -127,7 +127,7 @@ namespace Avalonia.Input
if (args.Type == RawPointerEventType.TouchUpdate)
{
target = gestureTarget ?? target;
- var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, (Visual)args.Root,
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, args.Root.RootElement,
args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point),
keyModifier, args.IntermediatePoints);
diff --git a/src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs b/src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs
deleted file mode 100644
index 24f0ccd82e..0000000000
--- a/src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Avalonia.Layout
-{
- ///
- /// A special layout root with enforced size for Arrange pass
- ///
- public interface IEmbeddedLayoutRoot : ILayoutRoot
- {
- Size AllocatedSize { get; }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs
index 5035d3a48d..560d0aab00 100644
--- a/src/Avalonia.Base/Layout/ILayoutManager.cs
+++ b/src/Avalonia.Base/Layout/ILayoutManager.cs
@@ -6,7 +6,6 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- [PrivateApi]
public interface ILayoutManager : IDisposable
{
///
diff --git a/src/Avalonia.Base/Layout/ILayoutRoot.cs b/src/Avalonia.Base/Layout/ILayoutRoot.cs
index efea5bfed8..8624194c42 100644
--- a/src/Avalonia.Base/Layout/ILayoutRoot.cs
+++ b/src/Avalonia.Base/Layout/ILayoutRoot.cs
@@ -5,22 +5,18 @@ namespace Avalonia.Layout
///
/// Defines the root of a layoutable tree.
///
- [NotClientImplementable]
- public interface ILayoutRoot
+ internal interface ILayoutRoot
{
- ///
- /// The size available to lay out the controls.
- ///
- Size ClientSize { get; }
-
///
/// The scaling factor to use in layout.
///
- double LayoutScaling { get; }
+ public double LayoutScaling { get; }
///
/// Associated instance of layout manager
///
- internal ILayoutManager LayoutManager { get; }
+ public ILayoutManager LayoutManager { get; }
+
+ public Layoutable RootVisual { get; }
}
}
diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs
index a342c654f9..c50053dc05 100644
--- a/src/Avalonia.Base/Layout/LayoutHelper.cs
+++ b/src/Avalonia.Base/Layout/LayoutHelper.cs
@@ -140,16 +140,14 @@ namespace Avalonia.Layout
///
/// The control.
/// Thrown when control has no root or returned layout scaling is invalid.
- public static double GetLayoutScale(Layoutable control)
- => control.VisualRoot is ILayoutRoot layoutRoot ? layoutRoot.LayoutScaling : 1.0;
+ public static double GetLayoutScale(Layoutable control) => control.GetLayoutRoot()?.LayoutScaling ?? 1.0;
///
/// Rounds a size to integer values for layout purposes, compensating for high DPI screen
/// coordinates by rounding the size up to the nearest pixel.
///
/// Input size.
- /// DPI along x-dimension.
- /// DPI along y-dimension.
+ /// The DPI scale.
/// Value of size that will be rounded under screen DPI.
///
/// This is a layout helper method. It takes DPI into account and also does not return
@@ -157,14 +155,8 @@ namespace Avalonia.Layout
/// associated with the UseLayoutRounding property and should not be used as a general rounding
/// utility.
///
- public static Size RoundLayoutSizeUp(Size size, double dpiScaleX, double dpiScaleY)
- {
- return new Size(RoundLayoutValueUp(size.Width, dpiScaleX), RoundLayoutValueUp(size.Height, dpiScaleY));
- }
-
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "The DPI scale should have been normalized.")]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static Size RoundLayoutSizeUp(Size size, double dpiScale)
+ public static Size RoundLayoutSizeUp(Size size, double dpiScale)
{
// If DPI == 1, don't use DPI-aware rounding.
return dpiScale == 1.0 ?
@@ -181,8 +173,7 @@ namespace Avalonia.Layout
/// coordinates.
///
/// Input thickness.
- /// DPI along x-dimension.
- /// DPI along y-dimension.
+ /// The DPI scale.
/// Value of thickness that will be rounded under screen DPI.
///
/// This is a layout helper method. It takes DPI into account and also does not return
@@ -190,19 +181,8 @@ namespace Avalonia.Layout
/// associated with the UseLayoutRounding property and should not be used as a general rounding
/// utility.
///
- public static Thickness RoundLayoutThickness(Thickness thickness, double dpiScaleX, double dpiScaleY)
- {
- return new Thickness(
- RoundLayoutValue(thickness.Left, dpiScaleX),
- RoundLayoutValue(thickness.Top, dpiScaleY),
- RoundLayoutValue(thickness.Right, dpiScaleX),
- RoundLayoutValue(thickness.Bottom, dpiScaleY)
- );
- }
-
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "The DPI scale should have been normalized.")]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static Thickness RoundLayoutThickness(Thickness thickness, double dpiScale)
+ public static Thickness RoundLayoutThickness(Thickness thickness, double dpiScale)
{
// If DPI == 1, don't use DPI-aware rounding.
return dpiScale == 1.0 ?
@@ -220,7 +200,7 @@ namespace Avalonia.Layout
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "The DPI scale should have been normalized.")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static Point RoundLayoutPoint(Point point, double dpiScale)
+ public static Point RoundLayoutPoint(Point point, double dpiScale)
{
// If DPI == 1, don't use DPI-aware rounding.
return dpiScale == 1.0 ?
@@ -245,10 +225,11 @@ namespace Avalonia.Layout
/// associated with the UseLayoutRounding property and should not be used as a general rounding
/// utility.
///
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "The DPI scale should have been normalized.")]
public static double RoundLayoutValue(double value, double dpiScale)
{
// If DPI == 1, don't use DPI-aware rounding.
- return MathUtilities.IsOne(dpiScale) ?
+ return dpiScale == 1.0 ?
Math.Round(value) :
Math.Round(value * dpiScale) / dpiScale;
}
@@ -266,10 +247,11 @@ namespace Avalonia.Layout
/// associated with the UseLayoutRounding property and should not be used as a general rounding
/// utility.
///
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "The DPI scale should have been normalized.")]
public static double RoundLayoutValueUp(double value, double dpiScale)
{
// If DPI == 1, don't use DPI-aware rounding.
- return MathUtilities.IsOne(dpiScale) ?
+ return dpiScale == 1.0 ?
Math.Ceiling(value) :
Math.Ceiling(RoundTo8Digits(value) * dpiScale) / dpiScale;
}
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index 411b747107..fa82ff1d31 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -9,6 +9,7 @@ using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
#nullable enable
@@ -17,11 +18,10 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- [PrivateApi]
- public class LayoutManager : ILayoutManager, IDisposable
+ internal class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 10;
- private readonly Layoutable _owner;
+ private readonly ILayoutRoot _owner;
private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid);
private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid);
private readonly List _toArrangeAfterMeasure = new();
@@ -34,7 +34,7 @@ namespace Avalonia.Layout
public LayoutManager(ILayoutRoot owner)
{
- _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner));
+ _owner = owner;
_invokeOnRender = ExecuteQueuedLayoutPass;
}
@@ -63,7 +63,7 @@ namespace Avalonia.Layout
#endif
}
- if (control.VisualRoot != _owner)
+ if (control.GetLayoutRoot() != _owner)
{
throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
}
@@ -93,7 +93,7 @@ namespace Avalonia.Layout
#endif
}
- if (control.VisualRoot != _owner)
+ if (control.GetLayoutRoot() != _owner)
{
throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
}
@@ -188,9 +188,12 @@ namespace Avalonia.Layout
try
{
+ if (_owner?.RootVisual == null)
+ return;
+ var root = _owner.RootVisual;
_running = true;
- Measure(_owner);
- Arrange(_owner);
+ Measure(root);
+ Arrange(root);
}
finally
{
@@ -300,7 +303,7 @@ namespace Avalonia.Layout
// control to be removed.
if (!control.IsMeasureValid)
{
- if (control is ILayoutRoot root)
+ if (control.GetLayoutRoot()?.RootVisual == control)
{
control.Measure(Size.Infinity);
}
@@ -329,9 +332,7 @@ namespace Avalonia.Layout
if (!control.IsArrangeValid)
{
- if (control is IEmbeddedLayoutRoot embeddedRoot)
- control.Arrange(new Rect(embeddedRoot.AllocatedSize));
- else if (control is ILayoutRoot root)
+ if (control.GetLayoutRoot()?.RootVisual == control)
control.Arrange(new Rect(control.DesiredSize));
else if (control.PreviousArrange != null)
{
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 9fa0f7689f..fedea332b6 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -168,7 +168,7 @@ namespace Avalonia.Layout
{
add
{
- if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
+ if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{
r.LayoutManager.RegisterEffectiveViewportListener(this);
}
@@ -180,7 +180,7 @@ namespace Avalonia.Layout
{
_effectiveViewportChanged -= value;
- if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+ if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r)
{
r.LayoutManager.UnregisterEffectiveViewportListener(this);
}
@@ -194,7 +194,7 @@ namespace Avalonia.Layout
{
add
{
- if (_layoutUpdated is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
+ if (_layoutUpdated is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
@@ -206,7 +206,7 @@ namespace Avalonia.Layout
{
_layoutUpdated -= value;
- if (_layoutUpdated is null && VisualRoot is ILayoutRoot r)
+ if (_layoutUpdated is null && this.GetLayoutRoot() is {} r)
{
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
}
@@ -220,7 +220,8 @@ namespace Avalonia.Layout
/// You should not usually need to call this method explictly, the layout manager will
/// schedule layout passes itself.
///
- public void UpdateLayout() => (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager?.ExecuteLayoutPass();
+
+ public void UpdateLayout() => this.GetLayoutManager()?.ExecuteLayoutPass();
///
/// Gets or sets the width of the element.
@@ -448,7 +449,7 @@ namespace Avalonia.Layout
if (IsAttachedToVisualTree)
{
- (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this);
+ this.GetLayoutManager()?.InvalidateMeasure(this);
InvalidateVisual();
}
OnMeasureInvalidated();
@@ -465,7 +466,7 @@ namespace Avalonia.Layout
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated arrange");
IsArrangeValid = false;
- (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this);
+ this.GetLayoutManager()?.InvalidateArrange(this);
InvalidateVisual();
}
}
@@ -793,7 +794,7 @@ namespace Avalonia.Layout
_isAttachingToVisualTree = false;
}
- if (e.Root is ILayoutRoot r)
+ if (this.GetLayoutRoot() is {} r)
{
if (_layoutUpdated is object)
{
@@ -809,7 +810,7 @@ namespace Avalonia.Layout
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
- if (e.Root is ILayoutRoot r)
+ if (this.GetLayoutRoot() is {} r)
{
if (_layoutUpdated is object)
{
@@ -852,7 +853,7 @@ namespace Avalonia.Layout
// they will need to be registered with the layout manager now that they
// are again effectively visible. If IsEffectivelyVisible becomes an observable
// property then we can piggy-pack on that; for the moment we do this manually.
- if (VisualRoot is ILayoutRoot layoutRoot)
+ if (this.GetLayoutRoot() is {} layoutRoot)
{
var count = VisualChildren.Count;
diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs
index 139129e623..07553a647e 100644
--- a/src/Avalonia.Base/Logging/LogArea.cs
+++ b/src/Avalonia.Base/Logging/LogArea.cs
@@ -20,6 +20,11 @@ namespace Avalonia.Logging
///
public const string Animations = nameof(Animations);
+ ///
+ /// The log event comes from the fonts system.
+ ///
+ public const string Fonts = nameof(Fonts);
+
///
/// The log event comes from the visual system.
///
diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index 9d1d0145d0..1f15820b9a 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -199,7 +199,7 @@ namespace Avalonia.Media
return true;
}
- var logger = Logger.TryGet(LogEventLevel.Debug, "FontManager");
+ var logger = Logger.TryGet(LogEventLevel.Debug, LogArea.Fonts);
logger?.Log(this,
$"Font family '{familyName}' could not be found. Present font families: [{string.Join(",", fontCollection)}]");
diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
index 4c535bdc0e..40176c88ff 100644
--- a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
+++ b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
+using Avalonia.Media.Fonts.Tables;
using Avalonia.Platform;
namespace Avalonia.Media.Fonts
@@ -55,35 +56,40 @@ namespace Avalonia.Media.Fonts
}
}
- //Try to find a match in any font family
- foreach (var pair in _glyphTypefaceCache)
+ return TryMatchInAnyFamily(isLastResort: false, out match) ||
+ TryMatchInAnyFamily(isLastResort: true, out match);
+
+ bool TryMatchInAnyFamily(bool isLastResort, out Typeface match)
{
- if (pair.Key == familyName)
+ //Try to find a match in any font family
+ foreach (var pair in _glyphTypefaceCache)
{
- //We already tried this before
- continue;
- }
-
- glyphTypefaces = pair.Value;
+ if (pair.Key == familyName)
+ {
+ //We already tried this before
+ continue;
+ }
- if (TryGetNearestMatch(glyphTypefaces, key, out var glyphTypeface))
- {
- if (glyphTypeface.CharacterToGlyphMap.TryGetGlyph(codepoint, out _))
+ if (TryGetNearestMatchCore(pair.Value, key, isLastResort, out var glyphTypeface))
{
- var platformTypeface = glyphTypeface.PlatformTypeface;
+ if (glyphTypeface.CharacterToGlyphMap.TryGetGlyph(codepoint, out _))
+ {
+ var platformTypeface = glyphTypeface.PlatformTypeface;
- // Found a match
- match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName),
- platformTypeface.Style,
- platformTypeface.Weight,
- platformTypeface.Stretch);
+ // Found a match
+ match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName),
+ platformTypeface.Style,
+ platformTypeface.Weight,
+ platformTypeface.Stretch);
- return true;
+ return true;
+ }
}
}
- }
- return false;
+ match = default;
+ return false;
+ }
}
public virtual bool TryCreateSyntheticGlyphTypeface(
@@ -128,7 +134,9 @@ namespace Avalonia.Media.Fonts
{
if (_fontManagerImpl.TryCreateGlyphTypeface(stream, fontSimulations, out var platformTypeface))
{
- syntheticGlyphTypeface = new GlyphTypeface(platformTypeface, fontSimulations);
+ syntheticGlyphTypeface = GlyphTypeface.TryCreate(platformTypeface, fontSimulations);
+ if (syntheticGlyphTypeface is null)
+ return false;
//Add the TypographicFamilyName to the cache
if (!string.IsNullOrEmpty(glyphTypeface.TypographicFamilyName))
@@ -272,16 +280,14 @@ namespace Avalonia.Media.Fonts
/// langword="false"/>.
public bool TryAddGlyphTypeface(Stream stream, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
- glyphTypeface = null;
-
if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
{
+ glyphTypeface = null;
return false;
}
- glyphTypeface = new GlyphTypeface(platformTypeface);
-
- return TryAddGlyphTypeface(glyphTypeface);
+ glyphTypeface = GlyphTypeface.TryCreate(platformTypeface);
+ return glyphTypeface is not null && TryAddGlyphTypeface(glyphTypeface);
}
///
@@ -315,13 +321,12 @@ namespace Avalonia.Media.Fonts
{
var stream = _assetLoader.Open(fontAsset);
- if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
+ if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface) ||
+ GlyphTypeface.TryCreate(platformTypeface) is not { } glyphTypeface)
{
continue;
}
- var glyphTypeface = new GlyphTypeface(platformTypeface);
-
var key = glyphTypeface.ToFontCollectionKey();
//Add TypographicFamilyName to the cache
@@ -353,14 +358,11 @@ namespace Avalonia.Media.Fonts
using var stream = File.OpenRead(source.LocalPath);
- if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
+ if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface) &&
+ GlyphTypeface.TryCreate(platformTypeface) is { } glyphTypeface &&
+ TryAddGlyphTypeface(glyphTypeface))
{
- var glyphTypeface = new GlyphTypeface(platformTypeface);
-
- if (TryAddGlyphTypeface(glyphTypeface))
- {
- result = true;
- }
+ result = true;
}
}
// If the path is a directory, load all font files from that directory
@@ -377,14 +379,11 @@ namespace Avalonia.Media.Fonts
{
using var stream = File.OpenRead(file);
- if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface))
+ if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface) &&
+ GlyphTypeface.TryCreate(platformTypeface) is { } glyphTypeface &&
+ TryAddGlyphTypeface(glyphTypeface))
{
- var glyphTypeface = new GlyphTypeface(platformTypeface);
-
- if (TryAddGlyphTypeface(glyphTypeface))
- {
- result = true;
- }
+ result = true;
}
}
}
@@ -565,7 +564,7 @@ namespace Avalonia.Media.Fonts
/// 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.
+ /// 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 .
@@ -574,10 +573,22 @@ namespace Avalonia.Media.Fonts
/// key, if a match is found; otherwise, .
/// if a matching is found; otherwise, .
- protected bool TryGetNearestMatch(IDictionary glyphTypefaces,
+ protected bool TryGetNearestMatch(IDictionary glyphTypefaces,
FontCollectionKey key, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
- if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
+ return TryGetNearestMatchCore(glyphTypefaces, key, isLastResort: false, out glyphTypeface)
+ || TryGetNearestMatchCore(glyphTypefaces, key, isLastResort: true, out glyphTypeface);
+ }
+
+ private static bool TryGetNearestMatchCore(
+ IDictionary glyphTypefaces,
+ FontCollectionKey key,
+ bool isLastResort,
+ [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
+ {
+ if (glyphTypefaces.TryGetValue(key, out glyphTypeface) &&
+ glyphTypeface != null &&
+ glyphTypeface.IsLastResort == isLastResort)
{
return true;
}
@@ -589,14 +600,14 @@ namespace Avalonia.Media.Fonts
if (key.Stretch != FontStretch.Normal)
{
- if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
+ if (TryFindStretchFallback(glyphTypefaces, key, isLastResort, out glyphTypeface))
{
return true;
}
if (key.Weight != FontWeight.Normal)
{
- if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, out glyphTypeface))
+ if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, isLastResort, out glyphTypeface))
{
return true;
}
@@ -605,12 +616,12 @@ namespace Avalonia.Media.Fonts
key = key with { Stretch = FontStretch.Normal };
}
- if (TryFindWeightFallback(glyphTypefaces, key, out glyphTypeface))
+ if (TryFindWeightFallback(glyphTypefaces, key, isLastResort, out glyphTypeface))
{
return true;
}
- if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
+ if (TryFindStretchFallback(glyphTypefaces, key, isLastResort, out glyphTypeface))
{
return true;
}
@@ -618,7 +629,7 @@ namespace Avalonia.Media.Fonts
//Take the first glyph typeface we can find.
foreach (var typeface in glyphTypefaces.Values)
{
- if (typeface != null)
+ if (typeface != null && isLastResort == typeface.IsLastResort)
{
glyphTypeface = typeface;
@@ -702,12 +713,14 @@ namespace Avalonia.Media.Fonts
/// A dictionary mapping font collection keys to their corresponding glyph typefaces. Used as the source for
/// searching fallback typefaces.
/// The font collection key specifying the desired font stretch and other font attributes to match.
+ /// Whether to match last resort fonts.
/// When this method returns, contains the found glyph typeface with a similar stretch if one exists; otherwise,
/// null.
/// true if a suitable fallback glyph typeface is found; otherwise, false.
private static bool TryFindStretchFallback(
IDictionary glyphTypefaces,
FontCollectionKey key,
+ bool isLastResort,
[NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@@ -718,7 +731,7 @@ namespace Avalonia.Media.Fonts
{
for (var i = 0; stretch + i < 9; i++)
{
- if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch + i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithStretch(stretch + i, out glyphTypeface))
{
return true;
}
@@ -728,13 +741,18 @@ namespace Avalonia.Media.Fonts
{
for (var i = 0; stretch - i > 1; i++)
{
- if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch - i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithStretch(stretch - i, out glyphTypeface))
{
return true;
}
}
}
+ bool TryGetWithStretch(int effectiveStretch, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
+ => glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)effectiveStretch }, out glyphTypeface) &&
+ glyphTypeface != null &&
+ glyphTypeface.IsLastResort == isLastResort;
+
return false;
}
@@ -749,12 +767,14 @@ namespace Avalonia.Media.Fonts
/// for a suitable fallback.
/// The font collection key specifying the desired font attributes, including weight, for which a fallback glyph
/// typeface is sought.
+ /// Whether to match last resort fonts.
/// When this method returns, contains the matching glyph typeface if a suitable fallback is found; otherwise,
/// null.
/// true if a fallback glyph typeface matching the requested weight is found; otherwise, false.
private static bool TryFindWeightFallback(
IDictionary glyphTypefaces,
FontCollectionKey key,
+ bool isLastResort,
[NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@@ -766,7 +786,7 @@ namespace Avalonia.Media.Fonts
//Look for available weights between the target and 500, in ascending order.
for (var i = 0; weight + i <= 500; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight + i, out glyphTypeface))
{
return true;
}
@@ -775,7 +795,7 @@ namespace Avalonia.Media.Fonts
//If no match is found, look for available weights less than the target, in descending order.
for (var i = 0; weight - i >= 100; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight - i, out glyphTypeface))
{
return true;
}
@@ -784,7 +804,7 @@ namespace Avalonia.Media.Fonts
//If no match is found, look for available weights greater than 500, in ascending order.
for (var i = 0; weight + i <= 900; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight + i, out glyphTypeface))
{
return true;
}
@@ -796,7 +816,7 @@ namespace Avalonia.Media.Fonts
{
for (var i = 0; weight - i >= 100; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight - i, out glyphTypeface))
{
return true;
}
@@ -805,7 +825,7 @@ namespace Avalonia.Media.Fonts
//If no match is found, look for available weights less than the target, in descending order.
for (var i = 0; weight + i <= 900; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight + i, out glyphTypeface))
{
return true;
}
@@ -817,7 +837,7 @@ namespace Avalonia.Media.Fonts
{
for (var i = 0; weight + i <= 900; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight + i, out glyphTypeface))
{
return true;
}
@@ -826,7 +846,7 @@ namespace Avalonia.Media.Fonts
//If no match is found, look for available weights less than the target, in descending order.
for (var i = 0; weight - i >= 100; i += 50)
{
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out glyphTypeface) && glyphTypeface != null)
+ if (TryGetWithWeight(weight - i, out glyphTypeface))
{
return true;
}
@@ -834,6 +854,11 @@ namespace Avalonia.Media.Fonts
}
return false;
+
+ bool TryGetWithWeight(int effectiveWeight, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
+ => glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)effectiveWeight }, out glyphTypeface) &&
+ glyphTypeface != null &&
+ glyphTypeface.IsLastResort == isLastResort;
}
void IDisposable.Dispose()
diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
index cf055e5d99..3c81e9890f 100644
--- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
@@ -52,7 +52,11 @@ namespace Avalonia.Media.Fonts
return false;
}
- glyphTypeface = new GlyphTypeface(platformTypeface);
+ glyphTypeface = GlyphTypeface.TryCreate(platformTypeface);
+ if (glyphTypeface is null)
+ {
+ return false;
+ }
//Add to cache with platform typeface family name first
TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface);
@@ -112,7 +116,10 @@ namespace Avalonia.Media.Fonts
}
// Not in cache yet: create glyph typeface and try to add it.
- var glyphTypeface = new GlyphTypeface(platformTypeface);
+ if (GlyphTypeface.TryCreate(platformTypeface) is not { } glyphTypeface)
+ {
+ return false;
+ }
// Try adding with the platform typeface family name first.
TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface);
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs
index 9a461afb0b..83db40ba62 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CharacterToGlyphMap.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Runtime.CompilerServices;
-using System.Text;
namespace Avalonia.Media.Fonts.Tables.Cmap
{
@@ -16,9 +14,10 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
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;
+ private readonly CmapFormat12Or13Table? _format12Or13;
+
+ internal CmapFormat Format { get; }
///
/// Initializes a new instance of the CharacterToGlyphMap class using the specified Format 4 cmap table.
@@ -27,21 +26,21 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal CharacterToGlyphMap(CmapFormat4Table table)
{
- _format = CmapFormat.Format4;
+ Format = CmapFormat.Format4;
_format4 = table;
- _format12 = null;
+ _format12Or13 = 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.
+ /// The Format 12 or 13 cmap table that defines the mapping from Unicode code points to glyph indices. Cannot be null.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal CharacterToGlyphMap(CmapFormat12Table table)
+ internal CharacterToGlyphMap(CmapFormat12Or13Table table)
{
- _format = CmapFormat.Format12;
- _format12 = table;
+ Format = table.Format;
+ _format12Or13 = table;
_format4 = null;
}
@@ -65,10 +64,10 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort GetGlyph(int codePoint)
{
- return _format switch
+ return Format switch
{
CmapFormat.Format4 => _format4!.GetGlyph(codePoint),
- CmapFormat.Format12 => _format12!.GetGlyph(codePoint),
+ CmapFormat.Format12 or CmapFormat.Format13 => _format12Or13!.GetGlyph(codePoint),
_ => 0
};
}
@@ -81,10 +80,10 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ContainsGlyph(int codePoint)
{
- return _format switch
+ return Format switch
{
CmapFormat.Format4 => _format4!.ContainsGlyph(codePoint),
- CmapFormat.Format12 => _format12!.ContainsGlyph(codePoint),
+ CmapFormat.Format12 or CmapFormat.Format13 => _format12Or13!.ContainsGlyph(codePoint),
_ => false
};
}
@@ -102,20 +101,20 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GetGlyphs(ReadOnlySpan codePoints, Span glyphIds)
{
- switch (_format)
+ switch (Format)
{
case CmapFormat.Format4:
_format4!.GetGlyphs(codePoints, glyphIds);
return;
case CmapFormat.Format12:
- _format12!.GetGlyphs(codePoints, glyphIds);
+ case CmapFormat.Format13:
+ _format12Or13!.GetGlyphs(codePoints, glyphIds);
return;
default:
glyphIds.Clear();
return;
}
}
-
///
/// Attempts to retrieve the glyph identifier corresponding to the specified Unicode code point.
@@ -127,10 +126,10 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetGlyph(int codePoint, out ushort glyphId)
{
- switch (_format)
+ switch (Format)
{
case CmapFormat.Format4: return _format4!.TryGetGlyph(codePoint, out glyphId);
- case CmapFormat.Format12: return _format12!.TryGetGlyph(codePoint, out glyphId);
+ case CmapFormat.Format12 or CmapFormat.Format13: return _format12Or13!.TryGetGlyph(codePoint, out glyphId);
default: glyphId = 0; return false;
}
}
@@ -142,7 +141,7 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CodepointRangeEnumerator GetMappedRanges()
{
- return new CodepointRangeEnumerator(_format, _format4, _format12);
+ return new CodepointRangeEnumerator(Format, _format4, _format12Or13);
}
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Table.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Or13Table.cs
similarity index 88%
rename from src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Table.cs
rename to src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Or13Table.cs
index b4440e7884..cc20e735d5 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Table.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapFormat12Or13Table.cs
@@ -1,30 +1,31 @@
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
+ internal sealed class CmapFormat12Or13Table
{
private readonly ReadOnlyMemory _table;
private readonly int _groupCount;
private readonly ReadOnlyMemory _groups;
+ public CmapFormat Format { get; }
+
///
/// 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)
+ public CmapFormat12Or13Table(ReadOnlyMemory table)
{
var reader = new BigEndianBinaryReader(table.Span);
ushort format = reader.ReadUInt16();
- Debug.Assert(format == 12, "Format must be 12.");
+ Debug.Assert(format is 12 or 13, "Format must be 12 or 13.");
+ Format = (CmapFormat)format;
ushort reserved = reader.ReadUInt16();
Debug.Assert(reserved == 0, "Reserved field must be 0.");
@@ -101,7 +102,7 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
// Optimization: check if codepoint is in the same group as previous
if (lastGroup >= 0 && codePoint >= lastStart && codePoint <= lastEnd)
{
- glyphIds[i] = (ushort)(lastStartGlyph + (codePoint - lastStart));
+ glyphIds[i] = CalcEffectiveGlyph(codePoint, lastStart, lastStartGlyph);
continue;
}
@@ -122,27 +123,13 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
lastEnd = ReadUInt32BE(groups, groupIndex, 4);
lastStartGlyph = ReadUInt32BE(groups, groupIndex, 8);
- glyphIds[i] = (ushort)(lastStartGlyph + (codePoint - lastStart));
+ glyphIds[i] = CalcEffectiveGlyph(codePoint, lastStart, lastStartGlyph);
}
}
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));
-
+ glyphId = this[codePoint];
return glyphId != 0;
}
@@ -180,10 +167,21 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
uint start = ReadUInt32BE(groups, groupIndex, 0);
uint startGlyph = ReadUInt32BE(groups, groupIndex, 8);
+ return CalcEffectiveGlyph(codePoint, start, startGlyph);
+ }
+ }
- // Calculate glyph index
- return (ushort)(startGlyph + (codePoint - start));
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ushort CalcEffectiveGlyph(int codePoint, uint start, uint startGlyph)
+ {
+ // Format 13, all codepoints in the group map to a single glyph
+ if (Format == CmapFormat.Format13)
+ {
+ return (ushort)startGlyph;
}
+
+ // Format 12, calculate glyph index
+ return (ushort)(startGlyph + (codePoint - start));
}
// Optimized binary search that works directly with cached span
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs
index f526658133..7774294e76 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CmapTable.cs
@@ -49,22 +49,28 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
}
// Try to find the best Format 12 subtable entry
- if (TryFindFormat12Entry(entries, out var format12Entry))
+ if (TryFindFormat12Or13Entry(entries, CmapFormat.Format12, out var format12Entry))
{
// Prefer Format 12 if available
- return new CharacterToGlyphMap(new CmapFormat12Table(format12Entry.GetSubtableMemory(table)));
+ return new CharacterToGlyphMap(new CmapFormat12Or13Table(format12Entry.GetSubtableMemory(table)));
}
- // Fallback to Format 4
+ // Then Format 4
if (TryFindFormat4Entry(entries, out var format4Entry))
{
return new CharacterToGlyphMap(new CmapFormat4Table(format4Entry.GetSubtableMemory(table)));
}
+ // Fallback to Format 13, which is a "last resort" format mapping many codepoints to a single glyph
+ if (TryFindFormat12Or13Entry(entries, CmapFormat.Format13, out var format13Entry))
+ {
+ return new CharacterToGlyphMap(new CmapFormat12Or13Table(format13Entry.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)
+ static bool TryFindFormat12Or13Entry(CmapSubtableEntry[] entries, CmapFormat expectedFormat, out CmapSubtableEntry result)
{
result = default;
var foundPlatformScore = int.MaxValue;
@@ -72,7 +78,7 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
foreach (var entry in entries)
{
- if (entry.Format != CmapFormat.Format12)
+ if (entry.Format != expectedFormat)
{
continue;
}
diff --git a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs
index b631c264d1..627ce1b6b6 100644
--- a/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs
+++ b/src/Avalonia.Base/Media/Fonts/Tables/Cmap/CodepointRangeEnumerator.cs
@@ -6,21 +6,21 @@ 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.
+ /// table in an OpenType or TrueType font. It supports Format 4, Format 12, and Format 13 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 readonly CmapFormat12Or13Table? _f12Or13;
private int _index;
- internal CodepointRangeEnumerator(CmapFormat format, CmapFormat4Table? f4, CmapFormat12Table? f12)
+ internal CodepointRangeEnumerator(CmapFormat format, CmapFormat4Table? f4, CmapFormat12Or13Table? f12Or13)
{
_format = format;
_f4 = f4;
- _f12 = f12;
+ _f12Or13 = f12Or13;
_index = -1;
}
@@ -52,8 +52,9 @@ namespace Avalonia.Media.Fonts.Tables.Cmap
return result;
}
case CmapFormat.Format12:
+ case CmapFormat.Format13:
{
- var result = _f12!.TryGetRange(_index, out var range);
+ var result = _f12Or13!.TryGetRange(_index, out var range);
Current = range;
diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs
index ca8e3fec16..fdbd56947e 100644
--- a/src/Avalonia.Base/Media/GlyphTypeface.cs
+++ b/src/Avalonia.Base/Media/GlyphTypeface.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Runtime.CompilerServices;
+using Avalonia.Logging;
using Avalonia.Media.Fonts;
using Avalonia.Media.Fonts.Tables;
using Avalonia.Media.Fonts.Tables.Cmap;
@@ -114,15 +114,19 @@ namespace Avalonia.Media
HeadTable.TryLoad(this, out var headTable);
+ IsLastResort = (headTable is not null && (headTable.Flags & HeadFlags.LastResortFont) != 0) ||
+ _cmapTable.Format == CmapFormat.Format13;
+
var postTable = PostTable.Load(this);
var isFixedPitch = postTable.IsFixedPitch;
var underlineOffset = postTable.UnderlinePosition;
var underlineSize = postTable.UnderlineThickness;
+ var designEmHeight = GetFontDesignEmHeight(headTable);
Metrics = new FontMetrics
{
- DesignEmHeight = headTable?.UnitsPerEm ?? 0,
+ DesignEmHeight = designEmHeight,
Ascent = ascent,
Descent = descent,
LineGap = lineGap,
@@ -222,6 +226,37 @@ namespace Avalonia.Media
}
}
+ private static ushort GetFontDesignEmHeight(HeadTable? headTable)
+ {
+ var unitsPerEm = headTable?.UnitsPerEm ?? 0;
+
+ // Bitmap fonts may specify 0 or miss the head table completely.
+ // Use 2048 as sensible default (used by most fonts).
+ if (unitsPerEm == 0)
+ unitsPerEm = 2048;
+
+ return unitsPerEm;
+ }
+
+ internal static GlyphTypeface? TryCreate(IPlatformTypeface typeface, FontSimulations fontSimulations = FontSimulations.None)
+ {
+ try
+ {
+ return new GlyphTypeface(typeface, fontSimulations);
+ }
+ catch (Exception ex)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Fonts)?.Log(
+ null,
+ "Could not create glyph typeface from platform typeface named {FamilyName} with simulations {Simulations}: {Exception}",
+ typeface.FamilyName,
+ fontSimulations,
+ ex);
+
+ return null;
+ }
+ }
+
///
/// Gets the family name of the font.
///
@@ -335,6 +370,11 @@ namespace Avalonia.Media
}
}
+ ///
+ /// Gets whether the font should be used as a last resort, if no other fonts matched.
+ ///
+ internal bool IsLastResort { get; }
+
///
/// Attempts to retrieve the horizontal advance width for the specified glyph.
///
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclLauncher.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclLauncher.cs
index 242b8e6d41..96f489a222 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclLauncher.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclLauncher.cs
@@ -49,13 +49,17 @@ internal class BclLauncher : ILauncher
}
else if (OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS())
{
- using var process = Process.Start(new ProcessStartInfo
+ var info = new ProcessStartInfo
{
FileName = OperatingSystemEx.IsWindows() ? urlOrFile : "open",
- Arguments = OperatingSystemEx.IsMacOS() ? $"{urlOrFile}" : "",
CreateNoWindow = true,
UseShellExecute = OperatingSystemEx.IsWindows()
- });
+ };
+ // Using the argument list avoids having to escape spaces and other special
+ // characters that are part of valid macos file and folder paths.
+ if (OperatingSystemEx.IsMacOS())
+ info.ArgumentList.Add(urlOrFile);
+ using var process = Process.Start(info);
return true;
}
else
diff --git a/src/Avalonia.Base/Reactive/SerialDisposableValue.cs b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs
deleted file mode 100644
index 9eaf6343bf..0000000000
--- a/src/Avalonia.Base/Reactive/SerialDisposableValue.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Threading;
-
-namespace Avalonia.Reactive;
-
-///
-/// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource.
-///
-internal sealed class SerialDisposableValue : IDisposable
-{
- private IDisposable? _current;
- private bool _disposed;
-
- public IDisposable? Disposable
- {
- get => _current;
- set
- {
- _current?.Dispose();
- _current = value;
-
- if (_disposed)
- {
- _current?.Dispose();
- _current = null;
- }
- }
- }
-
- public void Dispose()
- {
- _disposed = true;
- _current?.Dispose();
- }
-}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index dd81219168..66ee5579c8 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition;
///
internal class CompositingRenderer : IRendererWithCompositor, IHitTester
{
- private readonly IRenderRoot _root;
+ private readonly IPresentationSource _root;
private readonly Compositor _compositor;
private readonly RenderDataDrawingContext _recorder;
private readonly HashSet _dirty = new();
@@ -48,13 +48,12 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
///
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
///
- public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces)
+ public CompositingRenderer(IPresentationSource root, Compositor compositor, Func> surfaces)
{
_root = root;
_compositor = compositor;
_recorder = new(compositor);
CompositionTarget = compositor.CreateCompositionTarget(surfaces);
- CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
_update = Update;
Diagnostics = new RendererDiagnostics();
Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
@@ -188,13 +187,13 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
commit.Rendered.ContinueWith(_ => Dispatcher.UIThread.Post(() =>
{
_queuedSceneInvalidation = false;
- SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
+ SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(new Rect(_root.ClientSize)));
}, DispatcherPriority.Input), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
}
public void TriggerSceneInvalidatedForUnitTests(Rect rect) =>
- SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, rect));
+ SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(rect));
private void Update()
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
index 8a75681c68..52c34cbc41 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
@@ -14,7 +14,7 @@ public partial class Compositor
///
internal CompositionTarget CreateCompositionTarget(Func> surfaces)
{
- return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces, DiagnosticTextRenderer));
+ return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces));
}
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
index e8cd29f195..2398468456 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
@@ -37,29 +37,12 @@ namespace Avalonia.Rendering.Composition
private readonly object _pendingBatchLock = new();
private readonly List _pendingServerCompositorJobs = new();
private readonly List _pendingServerCompositorPostTargetJobs = new();
- private DiagnosticTextRenderer? _diagnosticTextRenderer;
private readonly Action _triggerCommitRequested;
internal IEasing DefaultEasing { get; }
internal Dispatcher Dispatcher { get; }
- private DiagnosticTextRenderer? DiagnosticTextRenderer
- {
- get
- {
- if (_diagnosticTextRenderer == null)
- {
- // We are running in some unit test context
- if (AvaloniaLocator.Current.GetService() == null)
- return null;
- _diagnosticTextRenderer = new(Typeface.Default.GlyphTypeface, 12.0);
- }
-
- return _diagnosticTextRenderer;
- }
- }
-
internal event Action? AfterCommit;
diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
index 6d9b9e23a8..7de58a6dba 100644
--- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
+++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
@@ -25,7 +25,7 @@ public static class ElementComposition
throw new InvalidOperationException("Composition visuals belong to different compositor instances");
visual.ChildCompositionVisual = compositionVisual;
- visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual);
+ visual.GetPresentationSource()?.Renderer.RecalculateChildren(visual);
}
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionTargetOverlays.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionTargetOverlays.cs
index a8c2908b88..9873fe6ff3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/CompositionTargetOverlays.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionTargetOverlays.cs
@@ -17,20 +17,16 @@ internal class CompositionTargetOverlays
private Rect? _oldFpsCounterRect;
private long _updateStarted;
private readonly ServerCompositionTarget _target;
- private readonly DiagnosticTextRenderer? _diagnosticTextRenderer;
- public CompositionTargetOverlays(
- ServerCompositionTarget target,
- DiagnosticTextRenderer? diagnosticTextRenderer)
+ public CompositionTargetOverlays(ServerCompositionTarget target)
{
_target = target;
- _diagnosticTextRenderer = diagnosticTextRenderer;
}
private RendererDebugOverlays DebugOverlays { get; set; }
private FpsCounter? FpsCounter
- => _fpsCounter ??= _diagnosticTextRenderer != null ? new FpsCounter(_diagnosticTextRenderer) : null;
+ => _fpsCounter ??= DiagnosticTextRenderer is { } diagnosticTextRenderer ? new FpsCounter(diagnosticTextRenderer) : null;
private FrameTimeGraph? LayoutTimeGraph
=> _layoutTimeGraph ??= CreateTimeGraph("Layout");
@@ -44,15 +40,29 @@ internal class CompositionTargetOverlays
private FrameTimeGraph? UpdateTimeGraph
=> _updateTimeGraph ??= CreateTimeGraph("TUpdate");
+ private DiagnosticTextRenderer? DiagnosticTextRenderer
+ {
+ get
+ {
+ if (field is null)
+ {
+ // We are running in some unit test context
+ if (AvaloniaLocator.Current.GetService() == null)
+ return null;
+ field = new DiagnosticTextRenderer(Typeface.Default.GlyphTypeface, 12.0);
+ }
+ return field;
+ }
+ }
public bool RequireLayer => DebugOverlays.HasAnyFlag(RendererDebugOverlays.DirtyRects);
private FrameTimeGraph? CreateTimeGraph(string title)
{
- if (_diagnosticTextRenderer == null)
+ if (DiagnosticTextRenderer is not { } diagnosticTextRenderer)
return null;
- return new FrameTimeGraph(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, _diagnosticTextRenderer);
+ return new FrameTimeGraph(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, diagnosticTextRenderer);
}
@@ -73,6 +83,8 @@ internal class CompositionTargetOverlays
if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) == 0)
{
_renderTimeGraph?.Reset();
+ _compositorUpdateTimeGraph?.Reset();
+ _updateTimeGraph?.Reset();
}
}
@@ -170,4 +182,4 @@ internal class CompositionTargetOverlays
LayoutTimeGraph?.AddFrameValue(lastLayoutPassTiming.Elapsed.TotalMilliseconds);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
index b7a1993510..0c8656604a 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
@@ -39,13 +39,12 @@ namespace Avalonia.Rendering.Composition.Server
public int RenderedVisuals { get; set; }
public int VisitedVisuals { get; set; }
- public ServerCompositionTarget(ServerCompositor compositor, Func> surfaces,
- DiagnosticTextRenderer? diagnosticTextRenderer)
+ public ServerCompositionTarget(ServerCompositor compositor, Func> surfaces)
: base(compositor)
{
_compositor = compositor;
_surfaces = surfaces;
- _overlays = new CompositionTargetOverlays(this, diagnosticTextRenderer);
+ _overlays = new CompositionTargetOverlays(this);
var platformRender = AvaloniaLocator.Current.GetService();
if (platformRender?.SupportsRegions == true && compositor.Options.UseRegionDirtyRectClipping != false)
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs
index 6f5f7b7572..fe6effbbd4 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs
@@ -46,20 +46,25 @@ partial class ServerCompositionVisual
public void UpdateAdorner()
{
GetAttHelper().EnqueuedForAdornerUpdate = false;
- var ownTransform = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix, Scale,
- RotationAngle, Orientation, Offset);
-
+
if (AdornedVisual != null && Parent != null)
{
+ // We ignore Visual's RenderTransform completely since it's set by AdornerLayer and can be out of sync
+ // with compositor-driver animations
+ var ownTransform = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, Matrix.Identity, Scale,
+ RotationAngle, Orientation, Offset);
if (
AdornerLayer_GetExpectedSharedAncestor(this) is {} sharedAncestor
&& ComputeTransformFromAncestor(AdornedVisual, sharedAncestor, out var adornerLayerToAdornedVisual))
- ownTransform = (ownTransform ?? Matrix.Identity) * adornerLayerToAdornedVisual;
+ _ownTransform = (ownTransform ?? Matrix.Identity) * adornerLayerToAdornedVisual;
else
- ownTransform = default(Matrix); // Don't render, something is broken
+ _ownTransform = default(Matrix); // Don't render, something is broken
}
- _ownTransform = ownTransform;
+ else
+ _ownTransform = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix, Scale,
+ RotationAngle, Orientation, Offset);
+
PropagateFlags(true, true);
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
index d2dd0346d6..8d8b862bb3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
@@ -94,7 +94,8 @@ partial class ServerCompositionVisual
if (visual.Opacity != 1)
{
- _opacityStack.Push(effectiveOpacity);
+ _opacityStack.Push(_opacity);
+ _opacity = effectiveOpacity;
_canvas.PushOpacity(visual.Opacity, visual._transformedSubTreeBounds.Value.ToRect());
}
diff --git a/src/Avalonia.Base/Rendering/IPresentationSource.cs b/src/Avalonia.Base/Rendering/IPresentationSource.cs
new file mode 100644
index 0000000000..bf5beebe49
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/IPresentationSource.cs
@@ -0,0 +1,46 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Layout;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering;
+
+// This interface serves two purposes:
+// 1) User-facing API (public members)
+// 2) A way to provide PresentationSource APIs to Avalonia.Base from Avalonia.Controls
+// without cyclic references (internal members)
+///
+/// Represents the host of the visual tree. On desktop platforms this is typically backed by a native window.
+///
+[NotClientImplementable]
+public interface IPresentationSource
+{
+ ///
+ /// The current root of the visual tree
+ ///
+ public Visual? RootVisual { get; }
+
+ ///
+ /// The scaling factor to use in rendering.
+ ///
+ public double RenderScaling { get; }
+
+ internal IPlatformSettings? PlatformSettings { get; }
+
+ internal IRenderer Renderer { get; }
+
+ internal IHitTester HitTester { get; }
+
+ internal IInputRoot InputRoot { get; }
+
+ internal ILayoutRoot LayoutRoot { get; }
+
+ ///
+ /// Gets the client size of the window.
+ ///
+ internal Size ClientSize { get; }
+
+ internal PixelPoint PointToScreen(Point point);
+ internal Point PointToClient(PixelPoint point);
+}
diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs
deleted file mode 100644
index 820840afbc..0000000000
--- a/src/Avalonia.Base/Rendering/IRenderRoot.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Avalonia.Metadata;
-
-namespace Avalonia.Rendering
-{
- ///
- /// Represents the root of a renderable tree.
- ///
- [NotClientImplementable]
- public interface IRenderRoot
- {
- ///
- /// Gets the client size of the window.
- ///
- Size ClientSize { get; }
-
- ///
- /// Gets the renderer for the window.
- ///
- public IRenderer Renderer { get; }
-
- public IHitTester HitTester { get; }
-
- ///
- /// The scaling factor to use in rendering.
- ///
- double RenderScaling { get; }
-
- ///
- /// Converts a point from screen to client coordinates.
- ///
- /// The point in screen device coordinates.
- /// The point in client coordinates.
- Point PointToClient(PixelPoint point);
-
- ///
- /// Converts a point from client to screen coordinates.
- ///
- /// The point in client coordinates.
- /// The point in screen device coordinates.
- PixelPoint PointToScreen(Point point);
- }
-}
diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs
index 0ca4459c97..56007c7e8c 100644
--- a/src/Avalonia.Base/Rendering/IRenderer.cs
+++ b/src/Avalonia.Base/Rendering/IRenderer.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Rendering
/// Defines the interface for a renderer.
///
[PrivateApi]
- public interface IRenderer : IDisposable
+ internal interface IRenderer : IDisposable
{
///
/// Gets a value indicating whether the renderer should draw specific diagnostics.
@@ -75,7 +75,7 @@ namespace Avalonia.Rendering
}
[PrivateApi]
- public interface IHitTester
+ internal interface IHitTester
{
///
/// Hit tests a location to find the visuals at the specified point.
diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
index 552ecb14ff..86fbae8158 100644
--- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
+++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
@@ -12,13 +12,9 @@ namespace Avalonia.Rendering
///
/// Initializes a new instance of the class.
///
- /// The render root that has been updated.
/// The updated area.
- public SceneInvalidatedEventArgs(
- IRenderRoot root,
- Rect dirtyRect)
+ public SceneInvalidatedEventArgs(Rect dirtyRect)
{
- RenderRoot = root;
DirtyRect = dirtyRect;
}
@@ -27,9 +23,5 @@ namespace Avalonia.Rendering
///
public Rect DirtyRect { get; }
- ///
- /// Gets the render root that has been invalidated.
- ///
- public IRenderRoot RenderRoot { get; }
}
}
diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs
index 1ebf4f6457..2c18836d75 100644
--- a/src/Avalonia.Base/Utilities/MathUtilities.cs
+++ b/src/Avalonia.Base/Utilities/MathUtilities.cs
@@ -1,19 +1,12 @@
using System;
using System.Runtime.CompilerServices;
-#if !BUILDTASK
-using Avalonia.Metadata;
-#endif
namespace Avalonia.Utilities
{
///
/// Provides math utilities not provided in System.Math.
///
-#if !BUILDTASK
- [Unstable("This API might be removed in next major version. Please use corresponding BCL APIs.")]
- public
-#endif
- static class MathUtilities
+ internal static class MathUtilities
{
// smallest such that 1.0+DoubleEpsilon != 1.0
internal const double DoubleEpsilon = 2.2204460492503131e-016;
diff --git a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs
deleted file mode 100644
index 068c73ba33..0000000000
--- a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-
-namespace Avalonia.Utilities
-{
- ///
- /// Stores either a single key value pair or constructs a dictionary when more than one value is stored.
- ///
- /// The type of the key.
- /// The type of the value.
- internal class SingleOrDictionary : IEnumerable>
- where TKey : notnull
- {
- private KeyValuePair? _singleValue;
- private Dictionary? dictionary;
-
- public void Add(TKey key, TValue value)
- {
- if (_singleValue != null)
- {
- dictionary = new Dictionary();
- ((ICollection>)dictionary).Add(_singleValue.Value);
- _singleValue = null;
- }
-
- if (dictionary != null)
- {
- dictionary.Add(key, value);
- }
- else
- {
- _singleValue = new KeyValuePair(key, value);
- }
- }
-
- public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
- {
- if (dictionary == null)
- {
- if (!_singleValue.HasValue || !EqualityComparer.Default.Equals(_singleValue.Value.Key, key))
- {
- value = default;
- return false;
- }
- else
- {
- value = _singleValue.Value.Value;
- return true;
- }
- }
- else
- {
- return dictionary.TryGetValue(key, out value);
- }
- }
-
- public IEnumerator> GetEnumerator()
- {
- if (dictionary == null)
- {
- if (_singleValue.HasValue)
- {
- return new SingleEnumerator>(_singleValue.Value);
- }
- }
- else
- {
- return dictionary.GetEnumerator();
- }
- return Enumerable.Empty>().GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public IEnumerable Values
- {
- get
- {
- if(dictionary == null)
- {
- if (_singleValue.HasValue)
- {
- return new[] { _singleValue.Value.Value };
- }
- }
- else
- {
- return dictionary.Values;
- }
- return Enumerable.Empty();
- }
- }
-
- private class SingleEnumerator : IEnumerator
- {
- private readonly T value;
- private int index = -1;
-
- public SingleEnumerator(T value)
- {
- this.value = value;
- }
-
- public T Current
- {
- get
- {
- if (index == 0)
- {
- return value;
- }
- else
- {
- throw new InvalidOperationException();
- }
- }
- }
-
- object? IEnumerator.Current => Current;
-
- public void Dispose()
- {
- }
-
- public bool MoveNext()
- {
- index++;
- return index < 1;
- }
-
- public void Reset()
- {
- index = -1;
- }
- }
-
- }
-}
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 843914dc1d..95d55754d3 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -6,10 +6,12 @@ using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Diagnostics;
+using Avalonia.Input;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
+using Avalonia.Platform;
using Avalonia.Reactive;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
@@ -124,7 +126,7 @@ namespace Avalonia
(s, h) => s.Invalidated -= h);
private Rect _bounds;
- private IRenderRoot? _visualRoot;
+ internal IPresentationSource? PresentationSource { get; private set; }
private Visual? _visualParent;
private bool _hasMirrorTransform;
private TargetWeakEventSubscriber? _affectsRenderWeakSubscriber;
@@ -154,8 +156,6 @@ namespace Avalonia
///
public Visual()
{
- _visualRoot = this as IRenderRoot;
-
// Disable transitions until we're added to the visual tree.
DisableTransitions();
@@ -339,7 +339,9 @@ namespace Avalonia
///
/// Gets the root of the visual tree, if the control is attached to a visual tree.
///
- protected internal IRenderRoot? VisualRoot => _visualRoot;
+ protected internal Visual? VisualRoot => PresentationSource?.RootVisual;
+
+ internal IInputRoot? GetInputRoot() => PresentationSource?.InputRoot;
internal RenderOptions RenderOptions
{
@@ -366,7 +368,7 @@ namespace Avalonia
///
/// Gets a value indicating whether this control is attached to a visual root.
///
- internal bool IsAttachedToVisualTree => VisualRoot != null;
+ internal bool IsAttachedToVisualTree => this.PresentationSource != null;
///
/// Gets the control's parent visual.
@@ -409,7 +411,7 @@ namespace Avalonia
///
public void InvalidateVisual()
{
- VisualRoot?.Renderer.AddDirty(this);
+ PresentationSource?.Renderer.AddDirty(this);
}
///
@@ -514,7 +516,7 @@ namespace Avalonia
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
- VisualRoot?.Renderer.RecalculateChildren(this);
+ PresentationSource?.Renderer.RecalculateChildren(this);
}
///
@@ -526,12 +528,8 @@ namespace Avalonia
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Attached to visual tree");
- _visualRoot = e.Root;
+ PresentationSource = e.PresentationSource;
RootedVisualChildrenCount++;
- if (_visualParent is null)
- {
- throw new InvalidOperationException("Visual was attached to the root without being added to the visual parent first.");
- }
if (RenderTransform is IMutableTransform mutableTransform)
{
@@ -539,27 +537,30 @@ namespace Avalonia
}
EnableTransitions();
- if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer)
+ if (PresentationSource.Renderer is IRendererWithCompositor compositingRenderer)
{
AttachToCompositor(compositingRenderer.Compositor);
}
InvalidateMirrorTransform();
- UpdateIsEffectivelyVisible(_visualParent.IsEffectivelyVisible);
+ UpdateIsEffectivelyVisible(_visualParent?.IsEffectivelyVisible ?? true);
OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual();
- _visualRoot.Renderer.RecalculateChildren(_visualParent);
+ if (_visualParent != null)
+ {
+ PresentationSource.Renderer.RecalculateChildren(_visualParent);
- if (ZIndex != 0)
- _visualParent.HasNonUniformZIndexChildren = true;
+ if (ZIndex != 0)
+ _visualParent.HasNonUniformZIndexChildren = true;
+ }
var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
for (var i = 0; i < visualChildrenCount; i++)
{
- if (visualChildren[i] is { } child && child._visualRoot != e.Root) // child may already have been attached within an event handler
+ if (visualChildren[i] is { } child && child.PresentationSource != e.PresentationSource) // child may already have been attached within an event handler
{
child.OnAttachedToVisualTreeCore(e);
}
@@ -574,8 +575,7 @@ namespace Avalonia
protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Detached from visual tree");
-
- _visualRoot = this as IRenderRoot;
+
RootedVisualChildrenCount--;
if (RenderTransform is IMutableTransform mutableTransform)
@@ -589,7 +589,9 @@ namespace Avalonia
DetachFromCompositor();
DetachedFromVisualTree?.Invoke(this, e);
- e.Root.Renderer.AddDirty(this);
+ PresentationSource?.Renderer.AddDirty(this);
+
+ PresentationSource = null;
var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
@@ -686,7 +688,7 @@ namespace Avalonia
parentVisual.HasNonUniformZIndexChildren = true;
sender?.InvalidateVisual();
- parent?.VisualRoot?.Renderer.RecalculateChildren(parent);
+ parent?.PresentationSource?.Renderer.RecalculateChildren(parent);
}
///
@@ -714,17 +716,15 @@ namespace Avalonia
var old = _visualParent;
_visualParent = value;
- if (_visualRoot is not null && old is not null)
+ if (PresentationSource is not null && old is not null)
{
- var e = new VisualTreeAttachmentEventArgs(old, _visualRoot);
+ var e = new VisualTreeAttachmentEventArgs(old, PresentationSource);
OnDetachedFromVisualTreeCore(e);
}
- if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true)
+ if (_visualParent?.IsAttachedToVisualTree == true)
{
- var root = this.FindAncestorOfType() ??
- throw new AvaloniaInternalException("Visual is atached to visual tree but root could not be found.");
- var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
+ var e = new VisualTreeAttachmentEventArgs(_visualParent, _visualParent.PresentationSource!);
OnAttachedToVisualTreeCore(e);
}
@@ -810,5 +810,26 @@ namespace Avalonia
HasMirrorTransform = shouldApplyMirrorTransform;
}
+
+ internal void SetPresentationSourceForRootVisual(IPresentationSource? presentationSource)
+ {
+ if(presentationSource == PresentationSource)
+ return;
+
+ if (PresentationSource != null)
+ {
+ if (presentationSource != null)
+ throw new InvalidOperationException(
+ "Visual is already attached to a presentation source. Only one presentation source can be attached to a visual tree.");
+ OnDetachedFromVisualTreeCore(new(null, PresentationSource));
+ }
+
+ PresentationSource = presentationSource;
+ if(PresentationSource != null)
+ {
+ var e = new VisualTreeAttachmentEventArgs(null, PresentationSource);
+ OnAttachedToVisualTreeCore(e);
+ }
+ }
}
}
diff --git a/src/Avalonia.Base/VisualExtensions.cs b/src/Avalonia.Base/VisualExtensions.cs
index e8dc5465d6..3df2eca039 100644
--- a/src/Avalonia.Base/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualExtensions.cs
@@ -16,10 +16,11 @@ namespace Avalonia
/// The point in client coordinates.
public static Point PointToClient(this Visual visual, PixelPoint point)
{
- var root = visual.VisualRoot ??
- throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
- var rootPoint = root.PointToClient(point);
- return ((Visual)root).TranslatePoint(rootPoint, visual)!.Value;
+ var source = visual.PresentationSource;
+ var root = source?.RootVisual ??
+ throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
+ var rootPoint = source.PointToClient(point);
+ return root.TranslatePoint(rootPoint, visual)!.Value;
}
///
@@ -30,10 +31,11 @@ namespace Avalonia
/// The point in screen coordinates.
public static PixelPoint PointToScreen(this Visual visual, Point point)
{
- var root = visual.VisualRoot ??
- throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
- var p = visual.TranslatePoint(point, (Visual)root);
- return root.PointToScreen(p!.Value);
+ var source = visual.PresentationSource;
+ var root = source?.RootVisual ??
+ throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
+ var p = visual.TranslatePoint(point, root);
+ return source.PointToScreen(p!.Value);
}
///
diff --git a/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs b/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs
index 9c33b1ffb9..e2a457bdc9 100644
--- a/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs
+++ b/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs
@@ -3,7 +3,7 @@ namespace Avalonia.VisualTree
///
/// Interface for controls that are at the root of a hosted visual tree, such as popups.
///
- public interface IHostedVisualTreeRoot
+ internal interface IHostedVisualTreeRoot
{
///
/// Gets the visual tree host.
diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
index b202a2e4b7..b97c15c4df 100644
--- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
+using Avalonia.Layout;
+using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Utilities;
@@ -331,9 +333,10 @@ namespace Avalonia.VisualTree
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
- var root = visual.GetVisualRoot();
+ var source = visual.GetPresentationSource();
+ var root = source?.RootVisual;
- if (root is null)
+ if (source is null || root is null)
{
return null;
}
@@ -342,7 +345,7 @@ namespace Avalonia.VisualTree
if (rootPoint.HasValue)
{
- return root.HitTester.HitTestFirst(rootPoint.Value, visual, filter);
+ return source.HitTester.HitTestFirst(rootPoint.Value, visual, filter);
}
return null;
@@ -380,14 +383,14 @@ namespace Avalonia.VisualTree
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
- var root = visual.GetVisualRoot();
+ var source = visual.GetPresentationSource();
- if (root is null)
+ if (source is null)
{
return Array.Empty();
}
- return root.HitTester.HitTest(p, visual, filter);
+ return source.HitTester.HitTest(p, visual, filter);
}
///
@@ -456,19 +459,26 @@ namespace Avalonia.VisualTree
return visual.VisualParent as T;
}
+
+ public static IPresentationSource? GetPresentationSource(this Visual visual) => visual.PresentationSource;
+
+ // TODO: Verify all usages, this is no longer necessary a TopLevel
+ internal static Visual? GetVisualRoot(this Visual visual) => visual.PresentationSource?.RootVisual;
+
+ internal static ILayoutRoot? GetLayoutRoot(this Visual visual) => visual.PresentationSource?.LayoutRoot;
+
///
- /// Gets the root visual for an .
+ /// Gets the layout manager for the visual's presentation source, or null if the visual is not attached to a visual root.
///
- /// The visual.
- ///
- /// The root visual or null if the visual is not rooted.
- ///
- public static IRenderRoot? GetVisualRoot(this Visual visual)
- {
- ThrowHelper.ThrowIfNull(visual, nameof(visual));
+ public static ILayoutManager? GetLayoutManager(this Visual visual) =>
+ visual.PresentationSource?.LayoutRoot.LayoutManager;
- return visual as IRenderRoot ?? visual.VisualRoot;
- }
+ ///
+ /// Attempts to obtain platform settings from the visual's root.
+ /// This will return null if the visual is not attached to a visual root.
+ ///
+ public static IPlatformSettings? GetPlatformSettings(this Visual visual) =>
+ visual.GetPresentationSource()?.PlatformSettings;
///
/// Returns a value indicating whether this control is attached to a visual root.
diff --git a/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs b/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
index c0e8b1613f..8b644e1b25 100644
--- a/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
+++ b/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using Avalonia.Rendering;
namespace Avalonia
@@ -12,22 +13,37 @@ namespace Avalonia
///
/// Initializes a new instance of the class.
///
- /// The parent that the visual is being attached to or detached from.
- /// The root visual.
- public VisualTreeAttachmentEventArgs(Visual parent, IRenderRoot root)
+ /// The parent that the visual's tree is being attached to or detached from.
+ /// Presentation source this visual is being attached to.
+ public VisualTreeAttachmentEventArgs(Visual? attachmentPoint, IPresentationSource presentationSource)
{
- Parent = parent ?? throw new ArgumentNullException(nameof(parent));
- Root = root ?? throw new ArgumentNullException(nameof(root));
+ RootVisual = presentationSource.RootVisual ??
+ throw new InvalidOperationException("PresentationSource must have a non-null RootVisual.");
+ AttachmentPoint = attachmentPoint;
+ PresentationSource = presentationSource ?? throw new ArgumentNullException(nameof(presentationSource));
}
///
- /// Gets the parent that the visual is being attached to or detached from.
+ /// Gets the parent that the visual's tree is being attached to or detached from, null means that
+ /// the entire tree is being attached to a PresentationSource
///
- public Visual Parent { get; }
+ public Visual? AttachmentPoint { get; }
+
+ [Obsolete("Use " + nameof(AttachmentPoint))]
+ public Visual? Parent => AttachmentPoint;
///
/// Gets the root of the visual tree that the visual is being attached to or detached from.
///
- public IRenderRoot Root { get; }
+ public IPresentationSource PresentationSource { get; }
+
+ [Obsolete("This was previously always returning TopLevel. This is no longer guaranteed. Use TopLevel.GetTopLevel(this) if you need a TopLevel or args.RootVisual if you are interested in the root of the visual tree.")]
+ public Visual Root => RootVisual;
+
+ ///
+ /// The root visual of the tree this visual is being attached to or detached from.
+ /// This is guaranteed to be non-null and will be the same as .
+ ///
+ public Visual RootVisual { get; set; }
}
}
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index bc6380a012..cf59791653 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -129,9 +129,9 @@
-
-
-
+
+
+
diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs
index 9259be8594..bc299bdbe1 100644
--- a/src/Avalonia.Controls/AppBuilder.cs
+++ b/src/Avalonia.Controls/AppBuilder.cs
@@ -60,6 +60,16 @@ namespace Avalonia
///
public string? RenderingSubsystemName { get; private set; }
+ ///
+ /// Gets or sets a method to call the initialize the text shaping subsystem.
+ ///
+ public Action? TextShapingSubsystemInitializer { get; private set; }
+
+ ///
+ /// Gets the name of the currently selected text shaping subsystem.
+ ///
+ public string? TextShapingSubsystemName { get; private set; }
+
///
/// Gets a method to call after the is setup.
///
@@ -224,6 +234,19 @@ namespace Avalonia
RenderingSubsystemName = name;
return Self;
}
+
+ ///
+ /// Specifies a text shaping subsystem to use.
+ ///
+ /// The method to call to initialize the text shaping subsystem.
+ /// The name of the text shaping subsystem.
+ /// An instance.
+ public AppBuilder UseTextShapingSubsystem(Action initializer, string name = "")
+ {
+ TextShapingSubsystemInitializer = initializer;
+ TextShapingSubsystemName = name;
+ return Self;
+ }
///
/// Specifies a runtime platform subsystem to use.
@@ -308,7 +331,12 @@ namespace Avalonia
if (RenderingSubsystemInitializer == null)
{
- throw new InvalidOperationException("No rendering system configured.");
+ throw new InvalidOperationException("No rendering system configured. Consider calling UseSkia().");
+ }
+
+ if (TextShapingSubsystemInitializer == null)
+ {
+ throw new InvalidOperationException("No text shaping system configured. Consider calling UseHarfBuzz().");
}
if (_appFactory == null)
@@ -333,6 +361,7 @@ namespace Avalonia
{
_optionsInitializers?.Invoke();
RuntimePlatformServicesInitializer?.Invoke();
+ TextShapingSubsystemInitializer?.Invoke();
RenderingSubsystemInitializer?.Invoke();
WindowingSubsystemInitializer?.Invoke();
AfterPlatformServicesSetupCallback?.Invoke(Self);
diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
index b32b60118c..bbce6286ce 100644
--- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Automation.Provider;
+using Avalonia.Metadata;
namespace Avalonia.Automation.Peers
{
@@ -283,12 +284,29 @@ namespace Avalonia.Automation.Peers
///
public string GetHelpText() => GetHelpTextCore() ?? string.Empty;
+ ///
+ /// Gets text that provides a placeholder for the element that is associated with this automation peer.
+ ///
+ ///
+ ///
+ /// -
+ /// Windows
+ /// No mapping.
+ ///
+ /// -
+ /// macOS
+ /// NSAccessibilityProtocol.accessibilityPlaceholderValue
+ ///
+ ///
+ ///
+ public string GetPlaceholderText() => GetPlaceholderTextCore() ?? string.Empty;
+
///
/// Gets the control type for the element that is associated with the UI Automation peer.
///
///
/// Gets the type of the element.
- ///
+ ///
///
/// -
/// Windows
@@ -381,13 +399,25 @@ namespace Avalonia.Automation.Peers
/// Windows
/// No mapping, but used internally to translate coordinates.
///
+ ///
+ ///
+ [PrivateApi]
+ public AutomationPeer? GetVisualRoot() => GetVisualRootCore();
+
+ ///
+ /// Gets the that is the root of this 's
+ /// visual tree.
+ ///
+ ///
+ ///
/// -
/// macOS
/// NSAccessibilityProtocol.accessibilityTopLevelUIElement
///
///
///
- public AutomationPeer? GetVisualRoot() => GetVisualRootCore();
+ [PrivateApi]
+ public AutomationPeer? GetAutomationRoot() => GetAutomationRootCore();
///
/// Gets a value that indicates whether the element that is associated with this automation
@@ -595,6 +625,7 @@ namespace Avalonia.Automation.Peers
protected abstract AutomationPeer? GetLabeledByCore();
protected abstract string? GetNameCore();
protected virtual string? GetHelpTextCore() => null;
+ protected virtual string? GetPlaceholderTextCore() => null;
protected virtual AutomationLandmarkType? GetLandmarkTypeCore() => null;
protected virtual int GetHeadingLevelCore() => 0;
protected virtual string? GetItemTypeCore() => null;
@@ -615,7 +646,7 @@ namespace Avalonia.Automation.Peers
return GetAutomationControlTypeCore();
}
- protected virtual AutomationPeer? GetVisualRootCore()
+ private protected virtual AutomationPeer? GetAutomationRootCore()
{
var peer = this;
var parent = peer.GetParent();
@@ -629,6 +660,8 @@ namespace Avalonia.Automation.Peers
return peer;
}
+ private protected virtual AutomationPeer? GetVisualRootCore() => GetAutomationRootCore();
+
protected virtual bool IsContentElementOverrideCore()
{
diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
index cdab4911f2..2dd896791f 100644
--- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
@@ -132,6 +132,12 @@ namespace Avalonia.Automation.Peers
result = ToolTip.GetTip(Owner) as string;
}
+ // Windows uses HelpText for placeholder text; macOS uses a separate property.
+ if (string.IsNullOrWhiteSpace(result))
+ {
+ result = GetPlaceholderTextCore();
+ }
+
return result;
}
protected override AutomationLandmarkType? GetLandmarkTypeCore() => AutomationProperties.GetLandmarkType(Owner);
@@ -141,10 +147,10 @@ namespace Avalonia.Automation.Peers
EnsureConnected();
return _parent;
}
-
- protected override AutomationPeer? GetVisualRootCore()
+
+ private protected override AutomationPeer? GetVisualRootCore()
{
- if (Owner.GetVisualRoot() is Control c)
+ if (Owner?.PresentationSource?.InputRoot?.FocusRoot is Control c)
return CreatePeerForElement(c);
return null;
}
@@ -262,11 +268,13 @@ namespace Avalonia.Automation.Peers
private void VisualChildrenChanged(object? sender, EventArgs e) => InvalidateChildren();
+ private protected virtual Visual? GetVisualParent() => Owner.GetVisualParent();
+
private void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == Visual.IsVisibleProperty)
{
- var parent = Owner.GetVisualParent();
+ var parent = GetVisualParent();
if (parent is Control c)
(GetOrCreate(c) as ControlAutomationPeer)?.InvalidateChildren();
}
@@ -297,7 +305,7 @@ namespace Avalonia.Automation.Peers
{
if (!_parentValid)
{
- var parent = Owner.GetVisualParent();
+ var parent = GetVisualParent();
while (parent is object)
{
@@ -305,6 +313,11 @@ namespace Avalonia.Automation.Peers
{
var parentPeer = GetOrCreate(c);
parentPeer.GetChildren();
+ if (parentPeer is ControlAutomationPeer controlPeer)
+ {
+ parent = controlPeer.GetVisualParent();
+ continue;
+ }
}
parent = parent.GetVisualParent();
diff --git a/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs
index cbc45d113e..1aeeeec3d1 100644
--- a/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs
@@ -58,8 +58,8 @@ namespace Avalonia.Controls.Automation.Peers
{
var oldFocus = _focus;
var c = focus as Control;
-
- _focus = c?.VisualRoot == Owner ? c : null;
+
+ _focus = Owner.IsVisualAncestorOf(c) ? c : null;
if (_focus != oldFocus)
{
@@ -89,5 +89,7 @@ namespace Avalonia.Controls.Automation.Peers
Owner.Closed -= OnClosed;
StopTrackingFocus();
}
+
+ private protected override Visual? GetVisualParent() => null;
}
}
diff --git a/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs
index 5dfd507e1b..cdbb3286d8 100644
--- a/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs
@@ -29,6 +29,7 @@ internal class InteropAutomationPeer : AutomationPeer
protected override AutomationPeer? GetLabeledByCore() => throw new NotImplementedException();
protected override string? GetNameCore() => throw new NotImplementedException();
protected override string? GetHelpTextCore() => throw new NotImplementedException();
+ protected override string? GetPlaceholderTextCore() => throw new NotImplementedException();
protected override IReadOnlyList GetOrCreateChildrenCore() => throw new NotImplementedException();
protected override AutomationPeer? GetParentCore() => _parent;
protected override bool HasKeyboardFocusCore() => throw new NotImplementedException();
diff --git a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs
index dacec48d36..4f41e088c6 100644
--- a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs
@@ -21,6 +21,8 @@ namespace Avalonia.Automation.Peers
return AutomationControlType.Edit;
}
+ protected override string? GetPlaceholderTextCore() => Owner.PlaceholderText;
+
protected virtual void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if(e.Property == TextBox.TextProperty)
diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
index ceb695422d..ac645c80dd 100644
--- a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
@@ -70,7 +70,7 @@ namespace Avalonia.Automation.Peers
var oldFocus = _focus;
var c = focus as Control;
- _focus = c?.VisualRoot == Owner ? c : null;
+ _focus = Owner.IsVisualAncestorOf(c) ? c : null;
if (_focus != oldFocus)
{
@@ -88,6 +88,11 @@ namespace Avalonia.Automation.Peers
OnFocusChanged(KeyboardDevice.Instance!.FocusedElement);
}
}
+
+ private protected override Visual? GetVisualParent()
+ {
+ return null;
+ }
}
}
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 3652acac45..9e2d1725ce 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -208,14 +208,14 @@ namespace Avalonia.Controls
if (IsDefault)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
ListenForDefault(inputElement);
}
}
if (IsCancel)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
ListenForCancel(inputElement);
}
@@ -229,14 +229,14 @@ namespace Avalonia.Controls
if (IsDefault)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
StopListeningForDefault(inputElement);
}
}
if (IsCancel)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
StopListeningForCancel(inputElement);
}
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index 2bfe3c9be2..3d548e15b9 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -299,6 +299,19 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+
+ // Reset mouse button tracking state. When the calendar popup closes
+ // (e.g. due to a programmatic window change during date selection),
+ // the PointerReleased event never fires, leaving these flags stuck.
+ // See https://github.com/AvaloniaUI/Avalonia/issues/18418
+ _isMouseLeftButtonDown = false;
+ _isMouseLeftButtonDownYearView = false;
+ }
+
private void SetDayTitles()
{
for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++)
diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs
index 5eb65d3396..430a8d4eaf 100644
--- a/src/Avalonia.Controls/Chrome/TitleBar.cs
+++ b/src/Avalonia.Controls/Chrome/TitleBar.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Controls.Chrome
_captionButtons = e.NameScope.Get("PART_CaptionButtons");
- if (VisualRoot is Window window)
+ if (TopLevel.GetTopLevel(this) is Window window)
{
_captionButtons?.Attach(window);
@@ -67,7 +67,7 @@ namespace Avalonia.Controls.Chrome
{
base.OnAttachedToVisualTree(e);
- if (VisualRoot is Window window)
+ if (TopLevel.GetTopLevel(this) is Window window)
{
_disposables = new CompositeDisposable(6)
{
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 86afcd06b9..6a6b9f6083 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -509,7 +509,7 @@ namespace Avalonia.Controls
if (e.Source == this
&& !e.Handled)
{
- var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration.OpenContextMenu;
+ var keymap = this.GetPlatformSettings()?.HotkeyConfiguration.OpenContextMenu;
if (keymap is null)
{
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 2f88e662a2..2f406252f7 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -8,6 +8,7 @@ using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Globalization;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -102,8 +103,6 @@ namespace Avalonia.Controls
SetCurrentValue(MaxYearProperty, new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset));
}
- private static void OnGridVisibilityChanged(DatePicker sender, AvaloniaPropertyChangedEventArgs e) => sender.SetGrid();
-
public string DayFormat
{
get => GetValue(DayFormatProperty);
@@ -406,7 +405,7 @@ namespace Avalonia.Controls
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
- (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+ this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
index ca5c43463d..95d9e82782 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
@@ -7,6 +7,7 @@ using System;
using System.Globalization;
using Avalonia.Controls.Utils;
using Avalonia.Automation.Peers;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -380,7 +381,7 @@ namespace Avalonia.Controls
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
- (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+ this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
diff --git a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
index 64978248e5..3f3a119a70 100644
--- a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
+++ b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Controls.Diagnostics
/// Diagnostics interface to retrieve an associated .
///
[NotClientImplementable]
- public interface IPopupHostProvider
+ internal interface IPopupHostProvider
{
///
/// The popup host.
diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
index 675b22edc7..ead981cdb6 100644
--- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
+++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
@@ -16,8 +16,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner();
- public static readonly StyledProperty MaterialProperty =
- AvaloniaProperty.Register(nameof(Material));
+ public static readonly StyledProperty MaterialProperty =
+ AvaloniaProperty.Register(nameof(Material));
private IDisposable? _subscription;
private IDisposable? _materialSubscription;
@@ -39,7 +39,7 @@ namespace Avalonia.Controls
set => SetValue(CornerRadiusProperty, value);
}
- public ExperimentalAcrylicMaterial Material
+ public ExperimentalAcrylicMaterial? Material
{
get => GetValue(MaterialProperty);
set => SetValue(MaterialProperty, value);
@@ -49,20 +49,29 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- var tl = (TopLevel)e.Root;
-
- _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
- .Subscribe(x =>
- {
- if (tl.PlatformImpl is null)
- return;
- if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
- Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
- else if (x == WindowTransparencyLevel.Blur)
- Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
- else if (x == WindowTransparencyLevel.AcrylicBlur)
- Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
- });
+ var tl = TopLevel.GetTopLevel(this);
+ if (tl != null)
+ {
+
+ _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
+ .Subscribe(x =>
+ {
+ if (tl.PlatformImpl is null || Material is null)
+ return;
+ if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
+ Material.PlatformTransparencyCompensationLevel =
+ tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
+ else if (x == WindowTransparencyLevel.Blur)
+ Material.PlatformTransparencyCompensationLevel =
+ tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
+ else if (x == WindowTransparencyLevel.AcrylicBlur)
+ Material.PlatformTransparencyCompensationLevel =
+ tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
+ });
+ }
+ else if (Material != null)
+ Material.PlatformTransparencyCompensationLevel = 1;
+
UpdateMaterialSubscription();
}
@@ -86,7 +95,9 @@ namespace Avalonia.Controls
if (visual is CompositionExperimentalAcrylicVisual v)
{
v.CornerRadius = CornerRadius;
- v.Material = (ImmutableExperimentalAcrylicMaterial)Material.ToImmutable();
+ v.Material = (Material?.ToImmutable()) is ImmutableExperimentalAcrylicMaterial material
+ ? material
+ : default(ImmutableExperimentalAcrylicMaterial);
}
}
diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
index b3ff18320a..dd0b5810bf 100644
--- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
@@ -344,7 +344,7 @@ namespace Avalonia.Controls.Primitives
return;
}
- if (Popup?.Host is PopupRoot && pArgs.Root is Visual eventRoot)
+ if (Popup?.Host is PopupRoot && pArgs.Root.RootElement is {} eventRoot)
{
// As long as the pointer stays within the enlargedPopupRect
// the flyout stays open. If it leaves, close it
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index df70cdf6d0..3eb4eae98d 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -2117,7 +2117,7 @@ namespace Avalonia.Controls
{
// DpiScale dpiScale = GetDpi();
// double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
- var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
+ var dpi = this.GetLayoutRoot()?.LayoutScaling ?? 1.0;
double[] roundingErrors = RoundingErrors;
double roundedTakenSize = 0;
@@ -2278,25 +2278,6 @@ namespace Avalonia.Controls
return null;
}
- ///
- /// Sorts row/column indices by rounding error if layout rounding is applied.
- ///
- /// Index, rounding error pair
- /// Index, rounding error pair
- /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise
- private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y)
- {
- if (x.Value < y.Value)
- {
- return -1;
- }
- else if (x.Value > y.Value)
- {
- return 1;
- }
- return 0;
- }
-
///
/// Calculates final (aka arrange) size for given range.
///
@@ -2985,88 +2966,6 @@ namespace Avalonia.Controls
}
}
- ///
- /// StarDistributionOrderIndexComparer.
- ///
- private class StarDistributionOrderIndexComparer : IComparer
- {
- private readonly IReadOnlyList definitions;
-
- internal StarDistributionOrderIndexComparer(IReadOnlyList definitions)
- {
- this.definitions = definitions ?? throw new ArgumentNullException(nameof(definitions));
- }
-
- public int Compare(object? x, object? y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase? definitionX = null;
- DefinitionBase? definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
-
- return result;
- }
- }
-
- ///
- /// DistributionOrderComparer.
- ///
- private class DistributionOrderIndexComparer : IComparer
- {
- private readonly IReadOnlyList definitions;
-
- internal DistributionOrderIndexComparer(IReadOnlyList definitions)
- {
- this.definitions = definitions ?? throw new ArgumentNullException(nameof(definitions));
- }
-
- public int Compare(object? x, object? y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase? definitionX = null;
- DefinitionBase? definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange;
- double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange;
- result = xprime.CompareTo(yprime);
- }
-
- return result;
- }
- }
-
///
/// RoundingErrorIndexComparer.
///
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index ba857687f6..df502207c6 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -13,6 +13,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -226,7 +227,7 @@ namespace Avalonia.Controls
ResizeDirection = resizeDirection,
SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection),
- Scaling = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1,
+ Scaling = this.GetLayoutRoot()?.LayoutScaling ?? 1,
};
// Store the rows and columns to resize on drag events.
diff --git a/src/Avalonia.Controls/IMenu.cs b/src/Avalonia.Controls/IMenu.cs
index b3ec77b108..426955b682 100644
--- a/src/Avalonia.Controls/IMenu.cs
+++ b/src/Avalonia.Controls/IMenu.cs
@@ -23,6 +23,6 @@ namespace Avalonia.Controls
///
/// Gets the root of the visual tree, if the control is attached to a visual tree.
///
- IRenderRoot? VisualRoot { get; }
+ TopLevel? TopLevel { get; }
}
}
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 9ef4b25c53..3ef75f4990 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -128,7 +128,7 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e)
{
- var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
+ var hotkeys = this.GetPlatformSettings()?.HotkeyConfiguration;
var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
if (!ctrl &&
diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs
index e134c5db19..08855d253c 100644
--- a/src/Avalonia.Controls/MaskedTextBox.cs
+++ b/src/Avalonia.Controls/MaskedTextBox.cs
@@ -6,6 +6,7 @@ using System.Linq;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -203,7 +204,7 @@ namespace Avalonia.Controls
return;
}
- var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
+ var keymap = this.GetPlatformSettings()?.HotkeyConfiguration;
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs
index 7ee2b27a52..9cb572722a 100644
--- a/src/Avalonia.Controls/Menu.cs
+++ b/src/Avalonia.Controls/Menu.cs
@@ -13,6 +13,8 @@ namespace Avalonia.Controls
///
public class Menu : MenuBase, IMainMenu
{
+ private IAccessKeyHandler? _accessKeyHandler;
+
private static readonly FuncTemplate DefaultPanel =
new (() => new StackPanel { Orientation = Orientation.Horizontal });
@@ -88,12 +90,18 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- var inputRoot = e.Root as TopLevel;
+ _accessKeyHandler = TopLevel.GetTopLevel(this)?.AccessKeyHandler;
+ _accessKeyHandler?.MainMenu = this;
+ }
- if (inputRoot?.AccessKeyHandler != null)
- {
- inputRoot.AccessKeyHandler.MainMenu = this;
- }
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ if (_accessKeyHandler?.MainMenu == this)
+ _accessKeyHandler.MainMenu = null;
+
+ _accessKeyHandler = null;
+
+ base.OnDetachedFromVisualTree(e);
}
protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index)
diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs
index 2c0792c028..bde5bb17de 100644
--- a/src/Avalonia.Controls/MenuBase.cs
+++ b/src/Avalonia.Controls/MenuBase.cs
@@ -75,7 +75,7 @@ namespace Avalonia.Controls
///
IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler;
- IRenderRoot? IMenu.VisualRoot => VisualRoot;
+ TopLevel? IMenu.TopLevel => TopLevel.GetTopLevel(this);
///
IMenuItem? IMenuElement.SelectedItem
diff --git a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
index 5571773a9d..1be05fbe36 100644
--- a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
+++ b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Controls
///
internal class MenuItemAccessKeyHandler : AccessKeyHandler
{
- protected override void OnSetOwner(IInputRoot owner)
+ protected override void OnSetOwner(InputElement owner)
{
owner.AddHandler(InputElement.TextInputEvent, OnTextInput);
}
diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs
index a6ad90dfcb..e3aaa22b5c 100644
--- a/src/Avalonia.Controls/NativeControlHost.cs
+++ b/src/Avalonia.Controls/NativeControlHost.cs
@@ -5,6 +5,7 @@ using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
+using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -12,7 +13,7 @@ namespace Avalonia.Controls
{
public class NativeControlHost : Control
{
- private TopLevel? _currentRoot;
+ private PresentationSource? _currentRoot;
private INativeControlHostImpl? _currentHost;
private INativeControlHostControlTopLevelAttachment? _attachment;
private IPlatformHandle? _nativeControlHandle;
@@ -43,7 +44,7 @@ namespace Avalonia.Controls
///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
- _currentRoot = e.Root as TopLevel;
+ _currentRoot = (PresentationSource)e.PresentationSource;
var visual = (Visual)this;
while (visual != null)
{
@@ -147,7 +148,7 @@ namespace Avalonia.Controls
var bounds = Bounds;
// Native window is not rendered by Avalonia
- var transformToVisual = this.TransformToVisual(_currentRoot);
+ var transformToVisual = _currentRoot.RootVisual != null ? this.TransformToVisual(_currentRoot.RootVisual) : null;
if (transformToVisual == null)
return null;
var position = new Rect(default, bounds.Size).TransformToAABB(transformToVisual.Value).Position;
diff --git a/src/Avalonia.Controls/NativeDock.cs b/src/Avalonia.Controls/NativeDock.cs
new file mode 100644
index 0000000000..d9d82ace4b
--- /dev/null
+++ b/src/Avalonia.Controls/NativeDock.cs
@@ -0,0 +1,28 @@
+namespace Avalonia.Controls
+{
+ ///
+ /// Allows native menu support on platforms where a can be attached to the dock.
+ ///
+ public static class NativeDock
+ {
+ ///
+ /// Defines the Menu attached property.
+ ///
+ public static readonly AttachedProperty MenuProperty =
+ AvaloniaProperty.RegisterAttached("Menu", typeof(NativeDock));
+
+ ///
+ /// Sets the value of the attached .
+ ///
+ /// The control to set the menu for.
+ /// The menu to set.
+ public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu);
+
+ ///
+ /// Gets the value of the attached .
+ ///
+ /// The control to get the menu for.
+ /// The menu of the control.
+ public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
+ }
+}
diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs
index 2f271ef34c..022511bafa 100644
--- a/src/Avalonia.Controls/NativeMenuBar.cs
+++ b/src/Avalonia.Controls/NativeMenuBar.cs
@@ -34,7 +34,7 @@ namespace Avalonia.Controls
?? this.FindDescendantOfType()
?? throw new InvalidOperationException("NativeMenuBar requires a MenuBase#PART_NativeMenuPresenter template part.");
- if (VisualRoot is TopLevel topLevel)
+ if (TopLevel.GetTopLevel(this) is {} topLevel)
{
SubscribeToToplevel(topLevel, _menu);
}
@@ -47,7 +47,7 @@ namespace Avalonia.Controls
if (_menu is null)
return;
- if (e.Root is TopLevel topLevel)
+ if (TopLevel.GetTopLevel(this) is {} topLevel)
{
SubscribeToToplevel(topLevel, _menu);
}
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index 8b699e2df7..f8d8c43d3c 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Controls.Platform
{
private readonly bool _isContextMenu;
private IDisposable? _inputManagerSubscription;
- private IRenderRoot? _root;
+ private TopLevel? _root;
private RadioButtonGroupManager? _groupManager;
public DefaultMenuInteractionHandler(bool isContextMenu)
@@ -300,7 +300,7 @@ namespace Avalonia.Controls.Platform
Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
- _root = Menu.VisualRoot;
+ _root = Menu.TopLevel;
if (_root is not null)
{
diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
index 615decc753..1a7b719499 100644
--- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs
+++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Platform
private DragDropEffects _allowedEffects;
private IDataTransfer? _draggedData;
- private TopLevel? _lastRoot;
+ private PresentationSource? _lastSource;
private Point _lastPosition;
private StandardCursorType? _lastCursorType;
private RawInputModifiers? _initialInputModifiers;
@@ -40,7 +40,7 @@ namespace Avalonia.Platform
if (_draggedData == null)
{
_draggedData = dataTransfer;
- _lastRoot = null;
+ _lastSource = null;
_lastPosition = default;
_allowedEffects = allowedEffects;
@@ -75,11 +75,12 @@ namespace Avalonia.Platform
_lastPosition = pt;
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData!, _allowedEffects, modifiers);
- var tl = (root as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault();
- tl?.PlatformImpl?.Input?.Invoke(rawEvent);
+ var source = root.RootElement.PresentationSource as PresentationSource;
+
+ source?.PlatformImpl?.Input?.Invoke(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
- UpdateCursor(tl, effect);
+ UpdateCursor(source, effect);
return effect;
}
@@ -105,12 +106,12 @@ namespace Avalonia.Platform
return StandardCursorType.No;
}
- private void UpdateCursor(TopLevel? root, DragDropEffects effect)
+ private void UpdateCursor(PresentationSource? root, DragDropEffects effect)
{
- if (_lastRoot != root)
+ if (_lastSource != root)
{
- _lastRoot?.SetCursorOverride(null);
- _lastRoot = root;
+ _lastSource?.SetCursorOverride(null);
+ _lastSource = root;
_lastCursorType = null;
}
@@ -127,8 +128,8 @@ namespace Avalonia.Platform
private void CancelDragging()
{
- if (_lastRoot != null)
- RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, RawInputModifiers.None);
+ if (_lastSource != null)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, _lastPosition, RawInputModifiers.None);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
}
@@ -137,16 +138,16 @@ namespace Avalonia.Platform
{
if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
{
- if (_lastRoot != null)
- RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers);
+ if (_lastSource != null)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, _lastPosition, e.Modifiers);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
e.Handled = true;
}
else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
{
- if (_lastRoot != null)
- RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
+ if (_lastSource != null)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastSource, _lastPosition, e.Modifiers);
}
}
@@ -195,10 +196,10 @@ namespace Avalonia.Platform
return;
}
- if (e.Root != _lastRoot)
+ if (e.Root != _lastSource)
{
- if (_lastRoot is Visual lr && e.Root is Visual r)
- RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers);
+ if (_lastSource?.RootElement is Visual lr && e.Root.RootElement is Visual r)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers);
RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
}
else
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs
new file mode 100644
index 0000000000..6acc904626
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs
@@ -0,0 +1,46 @@
+using Avalonia.Input;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource
+{
+ private Cursor? _cursor;
+ private Cursor? _cursorOverride;
+
+ private void UpdateCursor() => PlatformImpl?.SetCursor(_cursorOverride?.PlatformImpl ?? _cursor?.PlatformImpl);
+
+ private void SetCursor(Cursor? cursor)
+ {
+ _cursor = cursor;
+ UpdateCursor();
+ }
+
+ ///
+ /// This should only be used by InProcessDragSource
+ ///
+ internal void SetCursorOverride(Cursor? cursor)
+ {
+ _cursorOverride = cursor;
+ UpdateCursor();
+ }
+
+ IInputElement? IInputRoot.PointerOverElement
+ {
+ get => field;
+ set
+ {
+ if (field is AvaloniaObject old)
+ old.PropertyChanged -= PointerOverElement_PropertyChanged;
+ field = value;
+ if (field is AvaloniaObject @new)
+ @new.PropertyChanged += PointerOverElement_PropertyChanged;
+ SetCursor(value?.Cursor);
+ }
+ }
+
+ private void PointerOverElement_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == InputElement.CursorProperty)
+ SetCursor((sender as IInputElement)?.Cursor);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs
new file mode 100644
index 0000000000..3b48c7089d
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading;
+using Avalonia.Diagnostics;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource
+{
+ public IInputRoot InputRoot => this;
+
+ ///
+ /// Handles input from .
+ ///
+ private void HandleInputCore(object? state)
+ {
+ using var _ = Diagnostic.BeginLayoutInputPass();
+
+ var e = (RawInputEventArgs)state!;
+ if (e is RawPointerEventArgs pointerArgs)
+ {
+ var hitTestElement = RootElement.InputHitTest(pointerArgs.Position, enabledElementsOnly: false);
+
+ pointerArgs.InputHitTestResult = (hitTestElement, FirstEnabledAncestor(hitTestElement));
+ }
+
+ _inputManager?.ProcessInput(e);
+ }
+
+ private SendOrPostCallback _handleInputCore;
+
+ private void HandleInput(RawInputEventArgs e)
+ {
+ if (PlatformImpl != null)
+ {
+ Dispatcher.UIThread.Send(_handleInputCore, e);
+ }
+ else
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
+ this,
+ "PlatformImpl is null, couldn't handle input.");
+ }
+ }
+
+
+ private static IInputElement? FirstEnabledAncestor(IInputElement? hitTestElement)
+ {
+ var candidate = hitTestElement;
+ while (candidate?.IsEffectivelyEnabled == false)
+ {
+ candidate = (candidate as Visual)?.VisualParent as IInputElement;
+ }
+ return candidate;
+ }
+
+ public InputElement FocusRoot { get; }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
new file mode 100644
index 0000000000..156c76cc6f
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
@@ -0,0 +1,69 @@
+using System;
+using System.ComponentModel;
+using Avalonia.Layout;
+using Avalonia.Rendering;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource : ILayoutRoot
+{
+ private LayoutDiagnosticBridge? _layoutDiagnosticBridge;
+ public double LayoutScaling => RenderScaling;
+ public ILayoutManager LayoutManager { get; }
+ ILayoutRoot IPresentationSource.LayoutRoot => this;
+ Layoutable ILayoutRoot.RootVisual => RootVisual;
+
+ private ILayoutManager CreateLayoutManager()
+ {
+ var manager = new LayoutManager(this);
+ _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, manager);
+ _layoutDiagnosticBridge.SetupBridge();
+ return manager;
+ }
+
+
+ ///
+ /// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
+ ///
+ private sealed class LayoutDiagnosticBridge : IDisposable
+ {
+ private readonly RendererDiagnostics _diagnostics;
+ private readonly LayoutManager _layoutManager;
+ private bool _isHandling;
+
+ public LayoutDiagnosticBridge(RendererDiagnostics diagnostics, LayoutManager layoutManager)
+ {
+ _diagnostics = diagnostics;
+ _layoutManager = layoutManager;
+
+ diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
+ }
+
+ public void SetupBridge()
+ {
+ var needsHandling = (_diagnostics.DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0;
+ if (needsHandling != _isHandling)
+ {
+ _isHandling = needsHandling;
+ _layoutManager.LayoutPassTimed = needsHandling
+ ? timing => _diagnostics.LastLayoutPassTiming = timing
+ : null;
+ }
+ }
+
+ private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(RendererDiagnostics.DebugOverlays))
+ {
+ SetupBridge();
+ }
+ }
+
+ public void Dispose()
+ {
+ _diagnostics.PropertyChanged -= OnDiagnosticsPropertyChanged;
+ _layoutManager.LayoutPassTimed = null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs
new file mode 100644
index 0000000000..d017cb3f5c
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs
@@ -0,0 +1,29 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource
+{
+ private readonly Func _clientSizeProvider;
+ public CompositingRenderer Renderer { get; }
+ IRenderer IPresentationSource.Renderer => Renderer;
+ Visual IPresentationSource.RootVisual => RootVisual;
+ public IHitTester HitTester => HitTesterOverride ?? Renderer;
+ //TODO: Can we PLEASE get rid of this abomination in tests and use actual hit-testing engine instead?
+ public IHitTester? HitTesterOverride { get; set; }
+
+ public double RenderScaling => PlatformImpl?.RenderScaling ?? 1;
+ public Size ClientSize => _clientSizeProvider();
+
+ public void SceneInvalidated(object? sender, SceneInvalidatedEventArgs sceneInvalidatedEventArgs)
+ {
+ _pointerOverPreProcessor?.SceneInvalidated(sceneInvalidatedEventArgs.DirtyRect);
+ }
+
+ public PixelPoint PointToScreen(Point point) => PlatformImpl?.PointToScreen(point) ?? default;
+
+ public Point PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point) ?? default;
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
new file mode 100644
index 0000000000..85d5e38c12
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
@@ -0,0 +1,118 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Input.TextInput;
+using Avalonia.Layout;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource : IPresentationSource, IInputRoot, IDisposable
+{
+ public ITopLevelImpl? PlatformImpl { get; private set; }
+ private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
+ private readonly IDisposable? _pointerOverPreProcessorSubscription;
+ private readonly IInputManager? _inputManager;
+
+
+ internal FocusManager FocusManager { get; } = new();
+
+ public PresentationSource(InputElement rootVisual, InputElement defaultFocusVisual,
+ ITopLevelImpl platformImpl,
+ IAvaloniaDependencyResolver dependencyResolver, Func clientSizeProvider)
+ {
+ _clientSizeProvider = clientSizeProvider;
+
+ PlatformImpl = platformImpl;
+
+
+ _inputManager = TryGetService(dependencyResolver);
+ _handleInputCore = HandleInputCore;
+
+ PlatformImpl.SetInputRoot(this);
+ PlatformImpl.Input = HandleInput;
+
+ _pointerOverPreProcessor = new PointerOverPreProcessor(this);
+ _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
+
+ Renderer = new CompositingRenderer(this, PlatformImpl.Compositor, () => PlatformImpl.Surfaces ?? []);
+ Renderer.SceneInvalidated += SceneInvalidated;
+ LayoutManager = CreateLayoutManager();
+
+ RootVisual = rootVisual;
+ FocusRoot = defaultFocusVisual;
+ }
+
+ // In WPF it's a Visual and it's nullable. For now we have it as non-nullable InputElement since
+ // there are way too many things to update at once and the current goal is to decouple
+ // "visual tree root" concept from TopLevel
+ public InputElement RootVisual
+ {
+ get => field;
+ set
+ {
+ field?.SetPresentationSourceForRootVisual(null);
+ field = value;
+
+ field?.SetPresentationSourceForRootVisual(this);
+ Renderer.CompositionTarget.Root = field?.CompositionVisual;
+
+ FocusManager.SetContentRoot(value as IInputElement);
+ }
+ }
+
+
+ IFocusManager? IInputRoot.FocusManager => FocusManager;
+
+ IPlatformSettings? IPresentationSource.PlatformSettings => AvaloniaLocator.Current.GetService();
+
+ ITextInputMethodImpl? IInputRoot.InputMethod => PlatformImpl?.TryGetFeature();
+ public InputElement RootElement => RootVisual;
+
+
+ public void Dispose()
+ {
+ _layoutDiagnosticBridge?.Dispose();
+ _layoutDiagnosticBridge = null;
+ LayoutManager.Dispose();
+ Renderer.SceneInvalidated -= SceneInvalidated;
+ // We need to wait for the renderer to complete any in-flight operations
+ Renderer.Dispose();
+
+ PlatformImpl = null;
+ _pointerOverPreProcessor?.OnCompleted();
+ _pointerOverPreProcessorSubscription?.Dispose();
+ if (((IInputRoot)this).PointerOverElement is AvaloniaObject pointerOverElement)
+ pointerOverElement.PropertyChanged -= PointerOverElement_PropertyChanged;
+ }
+
+ ///
+ /// Tries to get a service from an , logging a
+ /// warning if not found.
+ ///
+ /// The service type.
+ /// The resolver.
+ /// The service.
+ private T? TryGetService(IAvaloniaDependencyResolver resolver) where T : class
+ {
+ var result = resolver.GetService();
+
+ if (result == null)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
+ this,
+ "Could not create {Service} : maybe Application.RegisterServices() wasn't called?",
+ typeof(T));
+ }
+
+ return result;
+ }
+
+ // TODO: Make popup positioner to use PresentationSource internally rather than TopLevel
+ public PixelPoint? GetLastPointerPosition(Visual topLevel)
+ {
+ return _pointerOverPreProcessor?.LastPosition;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index 7f59c9e570..da90a2e28a 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the attached property.
///
public static readonly AttachedProperty ShowAccessKeyProperty =
- AvaloniaProperty.RegisterAttached("ShowAccessKey", inherits: true);
+ AccessKeyHandler.ShowAccessKeyProperty.AddOwner();
///
/// The access key handler for the current window.
@@ -92,7 +92,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
- _accessKeys = (e.Root as TopLevel)?.AccessKeyHandler;
+ _accessKeys = TopLevel.GetTopLevel(this)?.AccessKeyHandler;
if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
diff --git a/src/Avalonia.Controls/Primitives/AdornerHelper.cs b/src/Avalonia.Controls/Primitives/AdornerHelper.cs
new file mode 100644
index 0000000000..c3f967da5c
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/AdornerHelper.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives;
+
+class AdornerHelper
+{
+
+ public static IDisposable SubscribeToAncestorPropertyChanges(Visual visual,
+ bool includeClip, Action changed)
+ {
+ return new AncestorPropertyChangesSubscription(visual, includeClip, changed);
+ }
+
+ private class AncestorPropertyChangesSubscription : IDisposable
+ {
+ private readonly Visual _visual;
+ private readonly bool _includeClip;
+ private readonly Action _changed;
+ private readonly EventHandler _propertyChangedHandler;
+ private readonly List _subscriptions = new List();
+ private bool _isDisposed;
+
+ public AncestorPropertyChangesSubscription(Visual visual, bool includeClip, Action changed)
+ {
+ _visual = visual;
+ _includeClip = includeClip;
+ _changed = changed;
+ _propertyChangedHandler = OnPropertyChanged;
+
+ _visual.AttachedToVisualTree += OnAttachedToVisualTree;
+ _visual.DetachedFromVisualTree += OnDetachedFromVisualTree;
+
+ if (_visual.IsAttachedToVisualTree)
+ {
+ SubscribeToAncestors();
+ }
+ }
+
+ private void SubscribeToAncestors()
+ {
+ UnsubscribeFromAncestors();
+
+ // Subscribe to the visual's own Bounds property
+ _visual.PropertyChanged += _propertyChangedHandler;
+ _subscriptions.Add(_visual);
+
+ // Walk up the ancestor chain
+ var ancestor = _visual.VisualParent;
+ while (ancestor != null)
+ {
+ if (ancestor is Visual visualAncestor)
+ {
+ visualAncestor.PropertyChanged += _propertyChangedHandler;
+ _subscriptions.Add(visualAncestor);
+ }
+ ancestor = ancestor.VisualParent;
+ }
+ }
+
+ private void UnsubscribeFromAncestors()
+ {
+ foreach (var subscription in _subscriptions)
+ {
+ subscription.PropertyChanged -= _propertyChangedHandler;
+ }
+ _subscriptions.Clear();
+ }
+
+ private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (!e.IsEffectiveValueChange)
+ return;
+
+ bool shouldNotify = false;
+
+ if (e.Property == Visual.RenderTransformProperty || e.Property == Visual.BoundsProperty)
+ {
+ shouldNotify = true;
+ }
+ else if (_includeClip)
+ {
+ if (e.Property == Visual.ClipToBoundsProperty ||
+ e.Property == Visual.ClipProperty) shouldNotify = true;
+ }
+
+ if (shouldNotify)
+ {
+ _changed();
+ }
+ }
+
+ private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
+ {
+ SubscribeToAncestors();
+ _changed();
+ }
+
+ private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
+ {
+ UnsubscribeFromAncestors();
+ _changed();
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed)
+ return;
+
+ _isDisposed = true;
+ UnsubscribeFromAncestors();
+ _visual.AttachedToVisualTree -= OnAttachedToVisualTree;
+ _visual.DetachedFromVisualTree -= OnDetachedFromVisualTree;
+ }
+ }
+
+ public static Geometry? CalculateAdornerClip(Visual adornedElement)
+ {
+ // Walk ancestor stack and calculate clip geometry relative to the current visual.
+ // If ClipToBounds = true, add extra RectangleGeometry for Bounds.Size
+
+ Geometry? result = null;
+ var ancestor = adornedElement;
+
+ while (ancestor != null)
+ {
+ if (ancestor is Visual visualAncestor)
+ {
+ Geometry? ancestorClip = null;
+
+ // Check if ancestor has ClipToBounds enabled
+ if (visualAncestor.ClipToBounds)
+ {
+ ancestorClip = new RectangleGeometry(new Rect(visualAncestor.Bounds.Size));
+ }
+
+ // Check if ancestor has explicit Clip geometry
+ if (visualAncestor.Clip != null)
+ {
+ if (ancestorClip != null)
+ {
+ ancestorClip = new CombinedGeometry(GeometryCombineMode.Intersect, ancestorClip, visualAncestor.Clip);
+ }
+ else
+ {
+ ancestorClip = visualAncestor.Clip;
+ }
+ }
+
+ // Transform the clip geometry to adorned element's coordinate space
+ if (ancestorClip != null)
+ {
+ var transform = visualAncestor.TransformToVisual(adornedElement);
+ if (transform.HasValue && !transform.Value.IsIdentity)
+ {
+ ancestorClip = ancestorClip.Clone();
+ var matrix = ancestorClip.Transform is { Value.IsIdentity: false }
+ ? transform.Value * ancestorClip.Transform.Value
+ : transform.Value;
+ ancestorClip.Transform = new MatrixTransform(matrix);
+ }
+
+ // Combine with existing result
+ if (result != null)
+ {
+ result = new CombinedGeometry(GeometryCombineMode.Intersect, result, ancestorClip);
+ }
+ else
+ {
+ result = ancestorClip;
+ }
+ }
+ }
+
+ ancestor = ancestor.VisualParent;
+ }
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index 412dd236ff..e96df62f2f 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Specialized;
+using Avalonia.Input.TextInput;
using Avalonia.Media;
using Avalonia.Reactive;
using Avalonia.VisualTree;
@@ -11,7 +12,6 @@ namespace Avalonia.Controls.Primitives
/// Adorners are always on top of the adorned element and are positioned to stay relative to the adorned element.
///
///
- /// TODO: Need to track position of adorned elements and move the adorner if they move.
///
public class AdornerLayer : Canvas
{
@@ -45,15 +45,20 @@ namespace Avalonia.Controls.Primitives
private static readonly AttachedProperty s_savedAdornerLayerProperty =
AvaloniaProperty.RegisterAttached("SavedAdornerLayer");
+ private TransformTrackingHelper _trackingHelper = new TransformTrackingHelper(false);
+
static AdornerLayer()
{
AdornedElementProperty.Changed.Subscribe(AdornedElementChanged);
AdornerProperty.Changed.Subscribe(AdornerChanged);
+ IsClipEnabledProperty.Changed.Subscribe(AdornerIsClipEnabledChanged);
}
- public AdornerLayer()
+ internal AdornerLayer()
{
Children.CollectionChanged += ChildrenCollectionChanged;
+ _trackingHelper.SetVisual(this);
+ _trackingHelper.MatrixChanged += delegate { InvalidateMeasure(); };
}
public static Visual? GetAdornedElement(Visual adorner)
@@ -199,9 +204,9 @@ namespace Avalonia.Controls.Primitives
{
var info = ao.GetValue(s_adornedElementInfoProperty);
- if (info != null && info.Bounds.HasValue)
+ if (info is { AdornedElement: not null })
{
- child.Measure(info.Bounds.Value.Bounds.Size);
+ child.Measure(info.AdornedElement.Bounds.Size);
}
else
{
@@ -223,12 +228,22 @@ namespace Avalonia.Controls.Primitives
var info = ao.GetValue(s_adornedElementInfoProperty);
var isClipEnabled = ao.GetValue(IsClipEnabledProperty);
- if (info != null && info.Bounds.HasValue)
+ var adorned = info?.AdornedElement;
+
+ if (adorned != null)
{
- child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform);
+ child.Arrange(new(adorned.Bounds.Size));
+ var transform = adorned.TransformToVisual(this);
+ // If somebody decides that having Margin on an adorner is a good idea,
+ // we need to compensate for element being positioned at non-(0,0) coords.
+ if (transform != null && child.Bounds.Position != default)
+ {
+ transform = Matrix.CreateTranslation(child.Bounds.Position) * transform.Value *
+ Matrix.CreateTranslation(-child.Bounds.Position);
+ }
+ child.RenderTransform = new MatrixTransform(transform ?? default);
child.RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Absolute);
- UpdateClip(child, info.Bounds.Value, isClipEnabled);
- child.Arrange(info.Bounds.Value.Bounds);
+ UpdateClip(child, adorned, isClipEnabled);
}
else
{
@@ -248,29 +263,22 @@ namespace Avalonia.Controls.Primitives
layer?.UpdateAdornedElement(adorner, adorned);
}
- private void UpdateClip(Control control, TransformedBounds bounds, bool isEnabled)
+ private static void AdornerIsClipEnabledChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var info = ((Visual)e.Sender).GetValue(s_adornedElementInfoProperty);
+ info?.UpdateSubscription();
+ info?.Layer?.InvalidateMeasure();
+ }
+
+ private void UpdateClip(Control control, Visual adorned, bool isEnabled)
{
if (!isEnabled)
{
control.Clip = null;
-
return;
}
- if (!(control.Clip is RectangleGeometry clip))
- {
- clip = new RectangleGeometry();
- control.Clip = clip;
- }
-
- var clipBounds = bounds.Bounds;
-
- if (bounds.Transform.HasInverse)
- {
- clipBounds = bounds.Clip.TransformToAABB(bounds.Transform.Invert());
- }
-
- clip.Rect = clipBounds;
+ control.Clip = AdornerHelper.CalculateAdornerClip(adorned);
}
private void ChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -313,24 +321,35 @@ namespace Avalonia.Controls.Primitives
{
if (info == null)
{
- info = new AdornedElementInfo();
+ info = new AdornedElementInfo(adorner);
adorner.SetValue(s_adornedElementInfoProperty, info);
}
- if (adorner.CompositionVisual != null)
- info.Subscription = adorned.GetObservable(BoundsProperty).Subscribe(x =>
- {
- info.Bounds = new TransformedBounds(new Rect(adorned.Bounds.Size), new Rect(adorned.Bounds.Size), Matrix.Identity);
- InvalidateMeasure();
- });
+ info.Layer = this;
+ info.AdornedElement = adorned;
+ info.UpdateSubscription();
}
}
- private class AdornedElementInfo
+ private class AdornedElementInfo(Visual adorner)
{
+ public AdornerLayer? Layer { get; set; }
public IDisposable? Subscription { get; set; }
+ public Visual? AdornedElement { get; set; }
- public TransformedBounds? Bounds { get; set; }
+ public void UpdateSubscription()
+ {
+ Subscription?.Dispose();
+ Subscription = null;
+ if (AdornedElement != null)
+ {
+ Subscription = AdornerHelper.SubscribeToAncestorPropertyChanges(AdornedElement,
+ GetIsClipEnabled(adorner), () =>
+ {
+ Layer?.InvalidateMeasure();
+ });
+ }
+ }
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
index 74b5beecad..d925a7a70c 100644
--- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
@@ -4,27 +4,8 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
- public class ChromeOverlayLayer : Panel
+ internal class ChromeOverlayLayer : Panel
{
- public static Panel? GetOverlayLayer(Visual visual)
- {
- foreach (var v in visual.GetVisualAncestors())
- if (v is VisualLayerManager vlm)
- if (vlm.OverlayLayer != null)
- return vlm.ChromeOverlayLayer;
- if (visual is TopLevel tl)
- {
- var layers = tl.GetVisualDescendants().OfType().FirstOrDefault();
- return layers?.ChromeOverlayLayer;
- }
-
- return null;
- }
-
- public void Add(Control c)
- {
- base.Children.Add(c);
- }
}
}
diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs
index 7ed87682a6..4d3659c96d 100644
--- a/src/Avalonia.Controls/Primitives/IPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs
@@ -15,8 +15,7 @@ namespace Avalonia.Controls.Primitives
/// () or an which is created
/// on an .
///
- [PrivateApi]
- public interface IPopupHost : IDisposable, IFocusScope
+ internal interface IPopupHost : IDisposable, IFocusScope
{
///
/// Gets or sets the fixed width of the popup.
diff --git a/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs b/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
index b7a41678db..196e2b98ed 100644
--- a/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
+++ b/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
@@ -1,6 +1,7 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives;
@@ -75,7 +76,7 @@ public static class ItemSelectionEventTriggers
public static bool HasToggleSelectionModifier(Visual selectable, RoutedEventArgs eventArgs) => HasModifiers(eventArgs, Hotkeys(selectable)?.CommandModifiers);
private static PlatformHotkeyConfiguration? Hotkeys(Visual element) =>
- (TopLevel.GetTopLevel(element)?.PlatformSettings ?? Application.Current?.PlatformSettings)?.HotkeyConfiguration;
+ (element.GetPlatformSettings() ?? Application.Current?.PlatformSettings)?.HotkeyConfiguration;
private static bool HasModifiers(RoutedEventArgs eventArgs, KeyModifiers? modifiers) =>
modifiers != null && eventArgs is IKeyModifiersEventArgs { KeyModifiers: { } eventModifiers } && eventModifiers.HasAllFlags(modifiers.Value);
diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
index 94ee99b019..2e4a1a4828 100644
--- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives
///
/// A layer that is used to dismiss a when the user clicks outside.
///
- public class LightDismissOverlayLayer : Border, ICustomHitTest
+ internal class LightDismissOverlayLayer : Border, ICustomHitTest
{
public IInputElement? InputPassThroughElement { get; set; }
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index f1f94facbc..6b1c611949 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -4,7 +4,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
- public class OverlayLayer : Canvas
+ internal class OverlayLayer : Canvas
{
protected override bool BypassFlowDirectionPolicies => true;
public Size AvailableSize { get; private set; }
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index d52e0e4d98..daab1d8539 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics;
using Avalonia.Input;
+using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
@@ -10,7 +11,7 @@ using Avalonia.Platform;
namespace Avalonia.Controls.Primitives
{
- public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup, IInputRoot
+ public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup
{
///
/// Defines the property.
@@ -21,6 +22,7 @@ namespace Avalonia.Controls.Primitives
private readonly OverlayLayer _overlayLayer;
private readonly ManagedPopupPositioner _positioner;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
+ internal IKeyboardNavigationHandler Tests_KeyboardNavigationHandler => _keyboardNavigationHandler!;
private Point _lastRequestedPosition;
private PopupPositionRequest? _popupPositionRequest;
private Size _popupSize;
@@ -29,7 +31,7 @@ namespace Avalonia.Controls.Primitives
static OverlayPopupHost()
=> KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(KeyboardNavigationMode.Cycle);
- public OverlayPopupHost(OverlayLayer overlayLayer)
+ internal OverlayPopupHost(OverlayLayer overlayLayer)
{
_overlayLayer = overlayLayer;
_positioner = new ManagedPopupPositioner(this);
@@ -39,7 +41,7 @@ namespace Avalonia.Controls.Primitives
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", Justification = "Explicit set")]
- public void SetChild(Control? control)
+ void IPopupHost.SetChild(Control? control)
{
Content = control;
}
@@ -59,39 +61,7 @@ namespace Avalonia.Controls.Primitives
get => false;
set { /* Not currently supported in overlay popups */ }
}
-
- private IInputRoot? InputRoot
- => TopLevel.GetTopLevel(this);
-
- IKeyboardNavigationHandler? IInputRoot.KeyboardNavigationHandler
- => _keyboardNavigationHandler;
-
- IFocusManager? IInputRoot.FocusManager
- => InputRoot?.FocusManager;
-
- IPlatformSettings? IInputRoot.PlatformSettings
- => InputRoot?.PlatformSettings;
-
- IInputElement? IInputRoot.PointerOverElement
- {
- get => InputRoot?.PointerOverElement;
- set
- {
- if (InputRoot is { } inputRoot)
- inputRoot.PointerOverElement = value;
- }
- }
-
- bool IInputRoot.ShowAccessKeys
- {
- get => InputRoot?.ShowAccessKeys ?? false;
- set
- {
- if (InputRoot is { } inputRoot)
- inputRoot.ShowAccessKeys = value;
- }
- }
-
+
///
internal override Interactive? InteractiveParent => Parent as Interactive;
@@ -116,7 +86,7 @@ namespace Avalonia.Controls.Primitives
_overlayLayer.Children.Remove(this);
}
- public void TakeFocus()
+ void IPopupHost.TakeFocus()
{
// Nothing to do here: overlay popups are implemented inside the window.
}
@@ -180,9 +150,8 @@ namespace Avalonia.Controls.Primitives
}
double IManagedPopupPositionerPopup.Scaling => 1;
-
- [PrivateApi]
- public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver, bool shouldUseOverlayLayer)
+
+ internal static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver, bool shouldUseOverlayLayer)
{
if (!shouldUseOverlayLayer)
{
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 76fa73f4c3..86d4fb746e 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -176,7 +176,7 @@ namespace Avalonia.Controls.Primitives
internal event EventHandler? Closing;
- public IPopupHost? Host => _openState?.PopupHost;
+ internal IPopupHost? Host => _openState?.PopupHost;
///
/// Gets or sets a hint to the window manager that a shadow should be added to the popup.
@@ -456,7 +456,7 @@ namespace Avalonia.Controls.Primitives
if (InheritsTransform)
{
- TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged)
+ TransformTrackingHelper.Track(placementTarget, true, PlacementTargetTransformChanged)
.DisposeWith(handlerCleanup);
}
else
@@ -676,7 +676,7 @@ namespace Avalonia.Controls.Primitives
{
var newTarget = change.GetNewValue() ?? this.FindLogicalAncestorOfType();
- if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel)
+ if (newTarget is null || TopLevel.GetTopLevel(newTarget) != _openState.TopLevel)
{
Close();
return;
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
index 82dead1ed5..ea38f7c7e3 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
@@ -570,19 +570,11 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
var target = positionRequest.Target;
if (target == null)
throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
- Matrix? matrix;
- if (TryGetAdorner(target, out var adorned, out var adornerLayer))
- {
- matrix = adorned!.TransformToVisual(topLevel) * target.TransformToVisual(adornerLayer!);
- }
- else
- {
- matrix = target.TransformToVisual(topLevel);
- }
-
+ Matrix? matrix = target.TransformToVisual(topLevel);
+
if (matrix == null)
{
- if (target.GetVisualRoot() == null)
+ if (!target.IsAttachedToVisualTree)
throw new InvalidOperationException("Target control is not attached to the visual tree");
throw new InvalidOperationException("Target control is not in the same tree as the popup parent");
}
@@ -591,25 +583,6 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
var anchorRect = positionRequest.AnchorRect ?? bounds;
return anchorRect.Intersect(bounds).TransformToAABB(matrix.Value);
}
-
- private static bool TryGetAdorner(Visual target, out Visual? adorned, out Visual? adornerLayer)
- {
- var element = target;
- while (element != null)
- {
- if (AdornerLayer.GetAdornedElement(element) is { } adornedElement)
- {
- adorned = adornedElement;
- adornerLayer = AdornerLayer.GetAdornerLayer(adorned);
- return true;
- }
- element = element.VisualParent;
- }
-
- adorned = null;
- adornerLayer = null;
- return false;
- }
}
}
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 6dc4627f0c..07ac06f15d 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -2,6 +2,7 @@ using System;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics;
+using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
@@ -189,7 +190,7 @@ namespace Avalonia.Controls.Primitives
return new Size(width, height);
}
- protected sealed override Size ArrangeSetBounds(Size size)
+ private protected sealed override Size ArrangeSetBounds(Size size)
{
if (_popupSize != size)
{
diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
index 91aa6b9bd0..18a50fb5c0 100644
--- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
+++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
@@ -1,9 +1,10 @@
using System.Collections.Generic;
+using Avalonia.Controls.Chrome;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
- public class VisualLayerManager : Decorator
+ public sealed class VisualLayerManager : Decorator
{
private const int AdornerZIndex = int.MaxValue - 100;
private const int ChromeZIndex = int.MaxValue - 99;
@@ -14,12 +15,9 @@ namespace Avalonia.Controls.Primitives
private ILogicalRoot? _logicalRoot;
private readonly List _layers = new();
- public static readonly StyledProperty ChromeOverlayLayerProperty =
- AvaloniaProperty.Register(nameof(ChromeOverlayLayer));
-
public bool IsPopup { get; set; }
- public AdornerLayer AdornerLayer
+ internal AdornerLayer AdornerLayer
{
get
{
@@ -30,30 +28,43 @@ namespace Avalonia.Controls.Primitives
}
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031",
- Justification = "A hack to make ChromeOverlayLayer lazily creatable. It is expected that GetValue(ChromeOverlayLayerProperty) alone won't work.")]
- public ChromeOverlayLayer ChromeOverlayLayer
- {
- get
- {
- var current = GetValue(ChromeOverlayLayerProperty);
- if (current is null)
- {
- var chromeOverlayLayer = new ChromeOverlayLayer();
- AddLayer(chromeOverlayLayer, ChromeZIndex);
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ EnsureChromeOverlayIfNeeded();
+
+ base.OnAttachedToVisualTree(e);
+ }
- SetValue(ChromeOverlayLayerProperty, chromeOverlayLayer);
+ void EnsureChromeOverlayIfNeeded()
+ {
+ // HACK: This is a replacement hack for the old set of hacks for TitleBar.
- current = chromeOverlayLayer;
+ // Check if we are attached direclty-ish to a Window (i. e. no other VisualLayerManager in between).
+ // If we are, then we are the "main" VisualLayerManager and should create the ChromeOverlayLayer and add titlebar there
+ var parent = VisualParent;
+ while (parent != null)
+ {
+ if(parent is VisualLayerManager)
+ break;
+ else if (parent is Window window)
+ {
+ if (FindLayer() == null)
+ {
+ var layer = new ChromeOverlayLayer();
+ AddLayer(layer, ChromeZIndex);
+ layer.Children.Add(new TitleBar());
+ }
+
+ break;
}
- return current;
+ parent = parent.VisualParent;
+
}
}
-
- public OverlayLayer? OverlayLayer
+
+ internal OverlayLayer? OverlayLayer
{
get
{
@@ -66,7 +77,7 @@ namespace Avalonia.Controls.Primitives
}
}
- public TextSelectorLayer? TextSelectorLayer
+ internal TextSelectorLayer? TextSelectorLayer
{
get
{
@@ -79,7 +90,7 @@ namespace Avalonia.Controls.Primitives
}
}
- public LightDismissOverlayLayer LightDismissOverlayLayer
+ internal LightDismissOverlayLayer LightDismissOverlayLayer
{
get
{
diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs
index 1ce36f14b5..8995f4762a 100644
--- a/src/Avalonia.Controls/RadioButton.cs
+++ b/src/Avalonia.Controls/RadioButton.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_groupManager?.Remove(this, GroupName);
- EnsureRadioGroupManager(e.Root);
+ EnsureRadioGroupManager(e.PresentationSource);
base.OnAttachedToVisualTree(e);
}
@@ -106,9 +106,9 @@ namespace Avalonia.Controls
}
[MemberNotNull(nameof(_groupManager))]
- private void EnsureRadioGroupManager(IRenderRoot? root = null)
+ private void EnsureRadioGroupManager(IPresentationSource? source = null)
{
- _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(root ?? this.GetVisualRoot());
+ _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(source ?? this.GetPresentationSource());
_groupManager.Add(this);
}
}
diff --git a/src/Avalonia.Controls/RadioButtonGroupManager.cs b/src/Avalonia.Controls/RadioButtonGroupManager.cs
index 80a942a2f0..c3d744c7c8 100644
--- a/src/Avalonia.Controls/RadioButtonGroupManager.cs
+++ b/src/Avalonia.Controls/RadioButtonGroupManager.cs
@@ -18,12 +18,12 @@ internal interface IRadioButton : ILogical
internal class RadioButtonGroupManager
{
private static readonly RadioButtonGroupManager s_default = new();
- private static readonly ConditionalWeakTable s_registeredVisualRoots = new();
+ private static readonly ConditionalWeakTable