diff --git a/.ncrunch/MobileSandbox.Browser.v3.ncrunchproject b/.ncrunch/MobileSandbox.Browser.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/MobileSandbox.Browser.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/WindowsInteropTest.net461.v3.ncrunchproject b/.ncrunch/WindowsInteropTest.net461.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/WindowsInteropTest.net461.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/WindowsInteropTest.net6.0-windows.v3.ncrunchproject b/.ncrunch/WindowsInteropTest.net6.0-windows.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/WindowsInteropTest.net6.0-windows.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index d2f2ee36d5..b802589fc7 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -6,17 +6,18 @@
"build": {
"type": "object",
"properties": {
- "Configuration": {
- "type": "string",
- "description": "configuration"
+ "api-baseline": {
+ "type": "string"
+ },
+ "configuration": {
+ "type": "string"
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
- "ForceNugetVersion": {
- "type": "string",
- "description": "force-nuget-version"
+ "force-nuget-version": {
+ "type": "string"
},
"Help": {
"type": "boolean",
@@ -89,17 +90,16 @@
"RunRenderTests",
"RunTests",
"RunToolsTests",
+ "ValidateApiDiff",
"ZipFiles"
]
}
},
- "SkipPreviewer": {
- "type": "boolean",
- "description": "skip-previewer"
+ "skip-previewer": {
+ "type": "boolean"
},
- "SkipTests": {
- "type": "boolean",
- "description": "skip-tests"
+ "skip-tests": {
+ "type": "boolean"
},
"Target": {
"type": "array",
@@ -124,10 +124,14 @@
"RunRenderTests",
"RunTests",
"RunToolsTests",
+ "ValidateApiDiff",
"ZipFiles"
]
}
},
+ "update-api-suppression": {
+ "type": "boolean"
+ },
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 5e770a0170..6089a06d4f 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -24,6 +24,7 @@
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.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",
diff --git a/Avalonia.sln b/Avalonia.sln
index 9670327d67..d5419365ac 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -135,7 +135,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interoperability", "src\Windows\Avalonia.Win32.Interoperability\Avalonia.Win32.Interoperability.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.Skia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
EndProject
@@ -251,6 +251,7 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators.Sandbox", "samples\Generators.Sandbox\Generators.Sandbox.csproj", "{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppWithoutLifetime", "samples\AppWithoutLifetime\AppWithoutLifetime.csproj", "{F8928267-688E-4A51-989C-612A72446D33}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo", "samples\SafeAreaDemo\SafeAreaDemo.csproj", "{6B60A970-D5D2-49C2-8BAB-F9C7973B74B6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.Android", "samples\SafeAreaDemo.Android\SafeAreaDemo.Android.csproj", "{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D}"
@@ -263,13 +264,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit", "src\Headless\Avalonia.Headless.NUnit\Avalonia.Headless.NUnit.csproj", "{ED976634-B118-43F8-8B26-0279C7A7044F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.NUnit", "src\Headless\Avalonia.Headless.NUnit\Avalonia.Headless.NUnit.csproj", "{ED976634-B118-43F8-8B26-0279C7A7044F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.NUnit.UnitTests", "tests\Avalonia.Headless.NUnit.UnitTests\Avalonia.Headless.NUnit.UnitTests.csproj", "{2999D79E-3C20-4A90-B651-CA7E0AC92D35}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit.UnitTests", "tests\Avalonia.Headless.NUnit.UnitTests\Avalonia.Headless.NUnit.UnitTests.csproj", "{2999D79E-3C20-4A90-B651-CA7E0AC92D35}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Browser", "samples\MobileSandbox.Browser\MobileSandbox.Browser.csproj", "{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Metal", "src\Avalonia.Metal\Avalonia.Metal.csproj", "{60B4ED1F-ECFA-453B-8A70-1788261C8355}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -606,10 +611,6 @@ Global
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
- {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -638,14 +639,14 @@ Global
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.Build.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.Build.0 = Release|Any CPU
{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -658,6 +659,14 @@ Global
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -727,9 +736,6 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
- {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
- {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
- {F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F8928267-688E-4A51-989C-612A72446D33} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@@ -737,11 +743,12 @@ Global
{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098}
- {ED976634-B118-43F8-8B26-0279C7A7044F} = {FF237916-7150-496B-89ED-6CA3292896E7}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
+ {ED976634-B118-43F8-8B26-0279C7A7044F} = {FF237916-7150-496B-89ED-6CA3292896E7}
{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{2999D79E-3C20-4A90-B651-CA7E0AC92D35} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/Documentation/build.md b/Documentation/build.md
index fd6b26337c..3fcaae8963 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -14,6 +14,18 @@ git submodule update --init
Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the latest version of the .NET Core SDK compatible with Avalonia UI. Make sure to download the SDK (not just the "runtime") package. The version compatible is indicated within the [global.json](https://github.com/AvaloniaUI/Avalonia/blob/master/global.json) file. Note that Avalonia UI does not always use the latest version and is hardcoded to use the last version known to be compatible (SDK releases may break the builds from time-to-time).
+### Installing necessary .NET Workloads
+
+.NET SDK requires developers to install workloads for each platform they are targeting.
+Since Avalonia targets pretty much every supported .NET platform, you need to install these workloads as well.
+Running it from the command line:
+```
+dotnet workload install android ios wasm-tools wasm-experimental
+```
+
+macOS workloads are not required to build Avalonia.
+Note: on Unix OS you need to run this command from sudo.
+
## Build and Run Avalonia
```
@@ -43,6 +55,25 @@ Build and run `ControlCatalog.NetCore` project to see the sample application.
>CSC : error CS0006: Metadata file 'C:\...\Avalonia\packages\Avalonia\bin\Debug\netcoreapp2.0\Avalonia.dll' could not be found
```
To correct this, right click on the `Avalonia.DesktopRuntime` project then press `Build` to build the project manually. Afterwards the solution should build normally and the ControlCatalog can be run.
+ * **Error MSB4062 GenerateAvaloniaResourcesTask**
+
+ Same as previous one, you need to manually build `Avalonia.Build.Tasks` project at least once.
+
+ Alternatively, you can build the solution once with Nuke.
+
+## Building packages with Nuke
+
+Install Nuke
+`dotnet tool install --global Nuke.GlobalTool --version 6.2.1`
+
+Build project:
+`nuke --target Compile --configuration Release`
+
+And run tests:
+`nuke --target RunTests --configuration Release`
+
+Or if you need to create nuget packages as well (it will compile and run tests automatically):
+`nuke --target Package --configuration Release`
# Linux/macOS
diff --git a/api/Avalonia.Controls.ColorPicker.nupkg.xml b/api/Avalonia.Controls.ColorPicker.nupkg.xml
new file mode 100644
index 0000000000..90271d6e98
--- /dev/null
+++ b/api/Avalonia.Controls.ColorPicker.nupkg.xml
@@ -0,0 +1,64 @@
+
+
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Fluent/ColorPicker.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Fluent/ColorPreviewer.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Fluent/ColorSlider.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Fluent/ColorSpectrum.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Fluent/ColorView.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Simple/ColorPicker.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Simple/ColorPreviewer.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Simple/ColorSlider.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Simple/ColorSpectrum.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Themes/Simple/ColorView.xaml
+ baseline/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+ target/netstandard2.0/Avalonia.Controls.ColorPicker.dll
+
+
\ No newline at end of file
diff --git a/api/Avalonia.Themes.Fluent.nupkg.xml b/api/Avalonia.Themes.Fluent.nupkg.xml
new file mode 100644
index 0000000000..f35bf5a629
--- /dev/null
+++ b/api/Avalonia.Themes.Fluent.nupkg.xml
@@ -0,0 +1,418 @@
+
+
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Accents/BaseResources.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Accents/FluentControlResources.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/AdornerLayer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/AutoCompleteBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Button.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ButtonSpinner.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Calendar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarDatePicker.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarDayButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CaptionButtons.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Carousel.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CheckBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ComboBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ComboBoxItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ContentControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ContextMenu.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DataValidationErrors.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DatePicker.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DateTimePickerShared.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DropDownButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/EmbeddableControlRoot.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Expander.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/FlyoutPresenter.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/GridSplitter.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ItemsControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Label.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ListBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ListBoxItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ManagedFileChooser.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Menu.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/MenuFlyoutPresenter.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/MenuItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/MenuScrollViewer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/NativeMenuBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/NotificationCard.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/NumericUpDown.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/OverlayPopupHost.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/PathIcon.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/PopupRoot.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ProgressBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RadioButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RefreshContainer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RefreshVisualizer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RepeatButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ScrollBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ScrollViewer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/SelectableTextBlock.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Separator.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Slider.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/SplitButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/SplitView.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabStrip.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabStripItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TextBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ThemeVariantScope.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TimePicker.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TitleBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ToggleButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ToggleSwitch.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ToolTip.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TransitioningContentControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TreeView.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TreeViewItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Window.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/WindowNotificationManager.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+
\ No newline at end of file
diff --git a/api/Avalonia.Themes.Simple.nupkg.xml b/api/Avalonia.Themes.Simple.nupkg.xml
new file mode 100644
index 0000000000..78d8c4b332
--- /dev/null
+++ b/api/Avalonia.Themes.Simple.nupkg.xml
@@ -0,0 +1,406 @@
+
+
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Accents/Base.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/AdornerLayer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/AutoCompleteBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Button.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ButtonSpinner.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Calendar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarDatePicker.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarDayButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CalendarItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CaptionButtons.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Carousel.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/CheckBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ComboBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ComboBoxItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ContentControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ContextMenu.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DataValidationErrors.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DatePicker.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DateTimePickerShared.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/DropDownButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/EmbeddableControlRoot.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Expander.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/FlyoutPresenter.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/GridSplitter.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ItemsControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Label.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ListBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ListBoxItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ManagedFileChooser.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Menu.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/MenuFlyoutPresenter.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/MenuItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/NativeMenuBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/NotificationCard.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/NumericUpDown.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/OverlayPopupHost.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/PathIcon.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/PopupRoot.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ProgressBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RadioButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RefreshContainer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RefreshVisualizer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/RepeatButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ScrollBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ScrollViewer.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/SelectableTextBlock.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Separator.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Slider.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/SplitButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/SplitView.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabStrip.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TabStripItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TextBox.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ThemeVariantScope.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TimePicker.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TitleBar.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ToggleButton.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ToggleSwitch.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/ToolTip.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TransitioningContentControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TreeView.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/TreeViewItem.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Window.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/WindowNotificationManager.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+
\ No newline at end of file
diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
new file mode 100644
index 0000000000..6a7d53544e
--- /dev/null
+++ b/api/Avalonia.nupkg.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ CP0006
+ P:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.ImportCompleted
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
\ No newline at end of file
diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props
index 00a1e3094b..a9af587a45 100644
--- a/build/CoreLibraries.props
+++ b/build/CoreLibraries.props
@@ -4,6 +4,7 @@
+
diff --git a/build/ExternalConsumers.props b/build/ExternalConsumers.props
index d79e951330..12c747f3f0 100644
--- a/build/ExternalConsumers.props
+++ b/build/ExternalConsumers.props
@@ -1,6 +1,7 @@
+
diff --git a/build/SourceLink.props b/build/SourceLink.props
index dd7ecc8d2a..b0f1f2c2cc 100644
--- a/build/SourceLink.props
+++ b/build/SourceLink.props
@@ -1,9 +1,9 @@
true
- false
+ true
+ snupkg
true
- $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
@@ -14,10 +14,6 @@
true
-
- embedded
-
-
diff --git a/dirs.proj b/dirs.proj
index d29aa61fcb..120212bd9f 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -14,11 +14,11 @@
+
-
-
+
+
-
diff --git a/native/Avalonia.Native/inc/com.h b/native/Avalonia.Native/inc/com.h
index df251514ef..42a989e050 100644
--- a/native/Avalonia.Native/inc/com.h
+++ b/native/Avalonia.Native/inc/com.h
@@ -28,6 +28,7 @@ typedef DWORD ULONG;
#define E_UNEXPECTED 0x8000FFFFL
#define E_HANDLE 0x80070006L
#define E_INVALIDARG 0x80070057L
+#define COR_E_INVALIDOPERATION 0x80131509L
struct IUnknown
{
diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h
index 47b0a3c5f2..017de01ee9 100644
--- a/native/Avalonia.Native/inc/comimpl.h
+++ b/native/Avalonia.Native/inc/comimpl.h
@@ -43,6 +43,18 @@ public:
_obj->AddRef();
}
}
+
+ ComPtr(TInterface* pObj, bool ownsHandle)
+ {
+ _obj = 0;
+
+ if (pObj)
+ {
+ _obj = pObj;
+ if(!ownsHandle)
+ _obj->AddRef();
+ }
+ }
ComPtr(const ComPtr& ptr)
{
@@ -92,6 +104,13 @@ public:
{
return &_obj;
}
+
+ void setNoAddRef(TInterface* value)
+ {
+ if(_obj != nullptr)
+ _obj->Release();
+ _obj = value;
+ }
operator TInterface*() const
{
diff --git a/native/Avalonia.Native/inc/noarc.h b/native/Avalonia.Native/inc/noarc.h
new file mode 100644
index 0000000000..c49d975ade
--- /dev/null
+++ b/native/Avalonia.Native/inc/noarc.h
@@ -0,0 +1,11 @@
+#import
+
+class CppAutoreleasePool
+{
+ void* _pool;
+public:
+ CppAutoreleasePool();
+ ~CppAutoreleasePool();
+};
+
+#define START_ARP_CALL CppAutoreleasePool __autoreleasePool
\ No newline at end of file
diff --git a/native/Avalonia.Native/inc/rendertarget.h b/native/Avalonia.Native/inc/rendertarget.h
index a59915777f..28572bc566 100644
--- a/native/Avalonia.Native/inc/rendertarget.h
+++ b/native/Avalonia.Native/inc/rendertarget.h
@@ -5,13 +5,21 @@
#include "avalonia-native.h"
@protocol IRenderTarget
--(void) setNewLayer: (CALayer*) layer;
--(HRESULT) setSwFrame: (AvnFramebuffer*) fb;
+
-(void) resize: (AvnPixelSize) size withScale: (float) scale;
--(AvnPixelSize) pixelSize;
--(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget;
+-(CALayer*) layer;
+
@end
@interface IOSurfaceRenderTarget : NSObject
-(IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
+-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget;
+-(IAvnSoftwareRenderTarget*) createSoftwareRenderTarget;
+-(HRESULT) setSwFrame: (AvnFramebuffer*) fb;
+-(void)consumeSurfaces;
@end
+
+@interface MetalRenderTarget : NSObject
+-(MetalRenderTarget*) initWithDevice: (IAvnMetalDevice*) device;
+-(void) getRenderTarget: (IAvnMetalRenderTarget**) ppv;
+@end
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
index eb0b528fd3..f11b027173 100644
--- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
+++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
@@ -43,6 +43,11 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
+ 64B1EA48E308E574685AFB07 /* metal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64B1EBEECBE13D8616D7C934 /* metal.mm */; };
+ 64B1ECA861163C0EFF0E502B /* noarc.h in Headers */ = {isa = PBXBuildFile; fileRef = 64B1E26F2B1B9C577BF52F06 /* noarc.h */; };
+ 64B1ED7C5433AC3B815FFCC5 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */; };
+ 64B1EE5DD4D882D587CE76CE /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */; };
+ 64B1EF3C757B71526FFAF436 /* noarc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; };
8D2F3512292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */; };
8D300D65292D0A6800320C49 /* AvnTextInputMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */; };
@@ -99,6 +104,11 @@
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; };
+ 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
+ 64B1E26F2B1B9C577BF52F06 /* noarc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = noarc.h; path = ../../inc/noarc.h; sourceTree = ""; };
+ 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = noarc.mm; sourceTree = ""; };
+ 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ 64B1EBEECBE13D8616D7C934 /* metal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = metal.mm; sourceTree = ""; };
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = ""; };
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethodDelegate.h; sourceTree = ""; };
8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethod.h; sourceTree = ""; };
@@ -126,6 +136,8 @@
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */,
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */,
+ 64B1ED7C5433AC3B815FFCC5 /* Metal.framework in Frameworks */,
+ 64B1EE5DD4D882D587CE76CE /* CoreGraphics.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -141,6 +153,8 @@
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,
AB1E522B217613570091CD71 /* OpenGL.framework */,
AB661C1D2148230F00291242 /* AppKit.framework */,
+ 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */,
+ 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -198,6 +212,9 @@
18391DB45C7D892E61BF388C /* WindowInterfaces.h */,
18391BB698579F40F1783F31 /* PopupImpl.mm */,
183910513F396141938832B5 /* PopupImpl.h */,
+ 64B1EBEECBE13D8616D7C934 /* metal.mm */,
+ 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */,
+ 64B1E26F2B1B9C577BF52F06 /* noarc.h */,
);
sourceTree = "";
};
@@ -230,6 +247,7 @@
18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */,
183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */,
18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */,
+ 64B1ECA861163C0EFF0E502B /* noarc.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -319,6 +337,8 @@
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
+ 64B1EA48E308E574685AFB07 /* metal.mm in Sources */,
+ 64B1EF3C757B71526FFAF436 /* noarc.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/native/Avalonia.Native/src/OSX/AvnView.h b/native/Avalonia.Native/src/OSX/AvnView.h
index 256caa70e9..e058656d3f 100644
--- a/native/Avalonia.Native/src/OSX/AvnView.h
+++ b/native/Avalonia.Native/src/OSX/AvnView.h
@@ -11,15 +11,16 @@
#include "KeyTransform.h"
@class AvnAccessibilityElement;
+@protocol IRenderTarget;
-@interface AvnView : NSView
+@interface AvnView : NSView
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
--(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
+-(void) setRenderTarget:(NSObject*)target;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end
diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm
index 86bacfb819..a4999b8df3 100644
--- a/native/Avalonia.Native/src/OSX/AvnView.mm
+++ b/native/Avalonia.Native/src/OSX/AvnView.mm
@@ -17,7 +17,7 @@
NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
- NSObject* _renderTarget;
+ NSObject* _currentRenderTarget;
AvnPlatformResizeReason _resizeReason;
AvnAccessibilityElement* _accessibilityChild;
NSRect _cursorRect;
@@ -41,15 +41,39 @@
- (void) updateRenderTarget
{
- [_renderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])];
- [self setNeedsDisplayInRect:[self frame]];
+ if(_currentRenderTarget) {
+ [_currentRenderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])];
+ [self setNeedsDisplayInRect:[self frame]];
+ }
+}
+
+
+-(void) setRenderTarget:(NSObject*)target
+{
+ if([self layer])
+ {
+ [self layer].delegate = nil;
+ }
+ _currentRenderTarget = target;
+ auto layer = [target layer];
+ [self setLayer: layer];
+ [layer setDelegate: self];
+ layer.needsDisplayOnBoundsChange = YES;
+ [self updateRenderTarget];
+}
+
+-(void)displayLayer: (CALayer*)layer
+{
+ [self updateLayer];
}
-(AvnView*) initWithParent: (WindowBaseImpl*) parent
{
self = [super init];
- _renderTarget = parent->renderTarget;
[self setWantsLayer:YES];
+ [self setLayerContentsPlacement: NSViewLayerContentsPlacementTopLeft];
+
+ [self setCanDrawSubviewsIntoLayer: NO];
[self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
_parent = parent;
@@ -77,12 +101,6 @@
return YES;
}
-- (void)setLayer:(CALayer *)layer
-{
- [_renderTarget setNewLayer: layer];
- [super setLayer: layer];
-}
-
- (BOOL)isOpaque
{
return YES;
@@ -164,14 +182,6 @@
return;
}
--(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
-{
- @autoreleasepool {
- [_renderTarget setSwFrame:fb];
- dispose->Release();
- }
-}
-
- (AvnPoint) translateLocalPoint:(AvnPoint)pt
{
pt.Y = [self bounds].size.height - pt.Y;
diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm
index 972d03d08c..39aa568134 100644
--- a/native/Avalonia.Native/src/OSX/PopupImpl.mm
+++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm
@@ -23,7 +23,7 @@ private:
END_INTERFACE_MAP()
virtual ~PopupImpl(){}
ComPtr WindowEvents;
- PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
+ PopupImpl(IAvnWindowEvents* events) : WindowBaseImpl(events)
{
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
@@ -47,11 +47,11 @@ public:
};
-extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
+extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events)
{
@autoreleasepool
{
- IAvnPopup* ptr = dynamic_cast(new PopupImpl(events, gl));
+ IAvnPopup* ptr = dynamic_cast(new PopupImpl(events));
return ptr;
}
}
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
index bc35ca670f..83c7aed5d3 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
@@ -27,7 +27,7 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl();
- WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false);
+ WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel = false);
virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@@ -81,13 +81,15 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override;
- virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) override;
-
virtual HRESULT SetCursor(IAvnCursor *cursor) override;
virtual void UpdateCursor();
- virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) override;
+ virtual HRESULT CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget **ppv) override;
+
+ virtual HRESULT CreateGlRenderTarget(IAvnGlContext* glContext, IAvnGlSurfaceRenderTarget **ppv) override;
+
+ virtual HRESULT CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget **ppv) override;
virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost **retOut) override;
@@ -118,7 +120,6 @@ private:
void CleanNSWindow ();
NSCursor *cursor;
- ComPtr _glContext;
bool hasPosition;
NSSize lastSize;
NSSize lastMinSize;
@@ -132,7 +133,7 @@ protected:
bool _shown;
public:
- NSObject *renderTarget;
+ NSObject *currentRenderTarget;
NSWindow * Window;
ComPtr BaseEvents;
ComPtr InputMethod;
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index edf6bcf508..1a4f979ce7 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -16,6 +16,7 @@
#import "WindowInterfaces.h"
#include "WindowBaseImpl.h"
#include "AvnTextInputMethod.h"
+#include "AvnView.h"
WindowBaseImpl::~WindowBaseImpl() {
@@ -23,12 +24,10 @@ WindowBaseImpl::~WindowBaseImpl() {
Window = nullptr;
}
-WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) {
+WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel) {
_shown = false;
_inResize = false;
BaseEvents = events;
- _glContext = gl;
- renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl];
View = [[AvnView alloc] initWithParent:this];
InputMethod = new AvnTextInputMethod(View);
StandardContainer = [[AutoFitContentView new] initWithContent:View];
@@ -152,6 +151,14 @@ HRESULT WindowBaseImpl::Hide() {
@autoreleasepool {
if (Window != nullptr) {
+ auto frame = [Window frame];
+
+ AvnPoint point;
+ point.X = frame.origin.x;
+ point.Y = frame.origin.y + frame.size.height;
+
+ lastPositionSet = ConvertPointY(point);
+ hasPosition = true;
[Window orderOut:Window];
}
@@ -448,13 +455,6 @@ HRESULT WindowBaseImpl::PointToScreen(AvnPoint point, AvnPoint *ret) {
}
}
-HRESULT WindowBaseImpl::ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) {
- START_COM_CALL;
-
- [View setSwRenderedFrame:fb dispose:dispose];
- return S_OK;
-}
-
HRESULT WindowBaseImpl::SetCursor(IAvnCursor *cursor) {
START_COM_CALL;
@@ -479,13 +479,49 @@ void WindowBaseImpl::UpdateCursor() {
}
}
-HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) {
+HRESULT WindowBaseImpl::CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget **ppv) {
+ START_COM_CALL;
+
+ if(![NSThread isMainThread])
+ return COR_E_INVALIDOPERATION;
+
+ if (View == NULL)
+ return E_FAIL;
+
+ auto target = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: nil];
+ *ppv = [target createSoftwareRenderTarget];
+ [View setRenderTarget: target];
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlContext* glContext, IAvnGlSurfaceRenderTarget **ppv) {
START_COM_CALL;
+ if(![NSThread isMainThread])
+ return COR_E_INVALIDOPERATION;
+
if (View == NULL)
return E_FAIL;
- *ppv = [renderTarget createSurfaceRenderTarget];
- return static_cast(*ppv == nil ? E_FAIL : S_OK);
+
+ auto target = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: glContext];
+ *ppv = [target createSurfaceRenderTarget];
+ [View setRenderTarget: target];
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget **ppv) {
+ START_COM_CALL;
+
+ if(![NSThread isMainThread])
+ return COR_E_INVALIDOPERATION;
+
+ if (View == NULL)
+ return E_FAIL;
+
+ auto target = [[MetalRenderTarget alloc] initWithDevice: device];
+ [View setRenderTarget: target];
+ [target getRenderTarget: ppv];
+ return S_OK;
}
HRESULT WindowBaseImpl::CreateNativeControlHost(IAvnNativeControlHost **retOut) {
@@ -615,11 +651,11 @@ HRESULT WindowBaseImpl::GetInputMethod(IAvnTextInputMethod **retOut) {
return S_OK;
}
-extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
+extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events)
{
@autoreleasepool
{
- IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
+ IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events);
return ptr;
}
}
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
index 5140124a17..049ef755ff 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -39,7 +39,7 @@ BEGIN_INTERFACE_MAP()
ComPtr WindowEvents;
- WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
+ WindowImpl(IAvnWindowEvents* events);
virtual HRESULT Show (bool activate, bool isDialog) override;
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index 925880534a..be761e0af7 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -9,7 +9,7 @@
#include "automation.h"
#include "WindowProtocol.h"
-WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
+WindowImpl::WindowImpl(IAvnWindowEvents *events) : WindowBaseImpl(events) {
_isEnabled = true;
_children = std::list();
_isClientAreaExtended = false;
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index 9cc9fc9523..88cdf4d9de 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -2,6 +2,7 @@
#include "AvnString.h"
@interface AvnAppDelegate : NSObject
-(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events;
+-(void) releaseEvents;
@end
NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@@ -15,6 +16,11 @@ ComPtr _events;
return self;
}
+- (void)releaseEvents
+{
+ _events = nil;
+}
+
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy)
@@ -105,6 +111,18 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDeleg
}
}
+extern void ReleaseAvnAppEvents()
+{
+ NSApplication* app = [AvnApplication sharedApplication];
+ id delegate = [app delegate];
+ if ([delegate isMemberOfClass:[AvnAppDelegate class]])
+ {
+ AvnAppDelegate* avnDelegate = delegate;
+ [avnDelegate releaseEvents];
+ [app setDelegate:nil];
+ }
+}
+
HRESULT AvnApplicationCommands::HideApp()
{
START_COM_CALL;
diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm
index d0c8d7a9db..9fe0ff3c60 100644
--- a/native/Avalonia.Native/src/OSX/automation.mm
+++ b/native/Avalonia.Native/src/OSX/automation.mm
@@ -73,6 +73,13 @@ private:
if (peer->IsRootProvider())
{
auto window = peer->RootProvider_GetWindow();
+
+ if (window == nullptr)
+ {
+ NSLog(@"IRootProvider.PlatformImpl returned null or a non-WindowBaseImpl.");
+ return nil;
+ }
+
auto holder = dynamic_cast(window);
auto view = holder->GetNSView();
return [[AvnRootAccessibilityElement alloc] initWithPeer:peer owner:view];
@@ -284,8 +291,8 @@ private:
- (id)accessibilityWindow
{
- id topLevel = [self accessibilityTopLevelUIElement];
- return [topLevel isKindOfClass:[NSWindow class]] ? topLevel : nil;
+ auto rootPeer = _peer->GetVisualRoot();
+ return [AvnAccessibilityElement acquire:rootPeer];
}
- (BOOL)isAccessibilityExpanded
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 4353737dc8..672525c64a 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -6,11 +6,13 @@
#import
#import
#include
+#include "noarc.h"
extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
extern void FreeAvnGCHandle(void* handle);
-extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
-extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
+extern void PostDispatcherCallback(IAvnActionCallback* cb);
+extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events);
+extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events);
extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
@@ -21,6 +23,7 @@ extern NSString* GetAvnCustomDataType();
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
+extern IAvnMetalDisplay* GetMetalDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
@@ -35,6 +38,7 @@ extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
+extern void ReleaseAvnAppEvents();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r);
@@ -56,6 +60,27 @@ template inline T* objc_cast(id from) {
return nil;
}
+template class ObjCWrapper {
+public:
+ T* Value;
+ ObjCWrapper(T* value)
+ {
+ Value = value;
+ }
+ operator T*() const
+ {
+ return Value;
+ }
+ T* operator->() const
+ {
+ return Value;
+ }
+ ~ObjCWrapper()
+ {
+ Value = nil;
+ }
+};
+
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@@ -79,5 +104,8 @@ public:
virtual HRESULT ShowAll() override;
virtual HRESULT HideOthers() override;
};
+#define NSApp [NSApplication sharedApplication]
+
+#define START_COM_ARP_CALL START_ARP_CALL; START_COM_CALL
#endif
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 4bfda4b531..3fddb72529 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -191,16 +191,28 @@ public:
@end
static ComPtr _deallocator;
+static ComPtr _dispatcher;
class AvaloniaNative : public ComSingleObject
{
public:
FORWARD_IUNKNOWN()
- virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override
+
+ virtual ~AvaloniaNative() override
+ {
+ ReleaseAvnAppEvents();
+ _deallocator = nullptr;
+ _dispatcher = nullptr;
+ }
+
+ virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator,
+ IAvnApplicationEvents* events,
+ IAvnDispatcher* dispatcher) override
{
START_COM_CALL;
_deallocator = deallocator;
+ _dispatcher = dispatcher;
@autoreleasepool{
[[ThreadingInitializer new] do];
}
@@ -213,7 +225,7 @@ public:
return (IAvnMacOptions*)new MacOptions();
}
- virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override
+ virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) override
{
START_COM_CALL;
@@ -221,12 +233,12 @@ public:
{
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
- *ppv = CreateAvnWindow(cb, gl);
+ *ppv = CreateAvnWindow(cb);
return S_OK;
}
};
- virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override
+ virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv) override
{
START_COM_CALL;
@@ -235,7 +247,7 @@ public:
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
- *ppv = CreateAvnPopup(cb, gl);
+ *ppv = CreateAvnPopup(cb);
return S_OK;
}
}
@@ -320,7 +332,22 @@ public:
return S_OK;
}
}
-
+
+ virtual HRESULT ObtainMetalDisplay(IAvnMetalDisplay** ppv) override
+ {
+ START_COM_CALL;
+ @autoreleasepool
+ {
+ auto rv = ::GetMetalDisplay();
+ if(rv == NULL)
+ return E_FAIL;
+ rv->AddRef();
+ *ppv = rv;
+ return S_OK;
+ }
+ }
+
+
virtual HRESULT CreateTrayIcon (IAvnTrayIcon** ppv) override
{
START_COM_CALL;
@@ -432,6 +459,11 @@ extern void FreeAvnGCHandle(void* handle)
_deallocator->FreeGCHandle(handle);
}
+extern void PostDispatcherCallback(IAvnActionCallback* cb)
+{
+ _dispatcher->Post(cb);
+}
+
NSSize ToNSSize (AvnSize s)
{
NSSize result;
diff --git a/native/Avalonia.Native/src/OSX/metal.mm b/native/Avalonia.Native/src/OSX/metal.mm
new file mode 100644
index 0000000000..8a66592ec4
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/metal.mm
@@ -0,0 +1,161 @@
+#import
+#import
+#import
+#include "common.h"
+#include "rendertarget.h"
+
+class AvnMetalDevice : public ComSingleObject
+{
+public:
+ id device;
+ id queue;
+ FORWARD_IUNKNOWN()
+
+ void *GetDevice() override {
+ return (__bridge void*) device;
+ }
+
+ void *GetQueue() override {
+ return (__bridge void*) queue;
+ }
+
+ AvnMetalDevice(id device, id queue) : device(device), queue(queue) {
+ }
+
+};
+
+
+class AvnMetalRenderSession : public ComSingleObject
+{
+ id _drawable;
+ id _queue;
+ id _texture;
+ CAMetalLayer* _layer;
+ AvnPixelSize _size;
+ double _scaling;
+public:
+ FORWARD_IUNKNOWN()
+
+ AvnMetalRenderSession(AvnMetalDevice* device, CAMetalLayer* layer, id drawable, const AvnPixelSize &size, double scaling)
+ : _drawable(drawable), _size(size), _scaling(scaling), _queue(device->queue),
+ _texture([drawable texture]) {
+ _layer = layer;
+ }
+
+ HRESULT GetPixelSize(AvnPixelSize *ret) override {
+ *ret = _size;
+ return 0;
+ }
+
+ double GetScaling() override {
+ return _scaling;
+ }
+
+ void *GetTexture() override {
+ return (__bridge void*) _texture;
+ }
+
+ ~AvnMetalRenderSession()
+ {
+ auto buffer = [_queue commandBuffer];
+ [buffer presentDrawable: _drawable];
+ [buffer commit];
+ }
+};
+
+class AvnMetalRenderTarget : public ComSingleObject
+{
+ CAMetalLayer* _layer;
+ double _scaling = 1;
+ AvnPixelSize _size = {1,1};
+ ComPtr _device;
+public:
+ double PendingScaling = 1;
+ AvnPixelSize PendingSize = {1,1};
+ FORWARD_IUNKNOWN()
+ AvnMetalRenderTarget(CAMetalLayer* layer, ComPtr device)
+ {
+ _layer = layer;
+ _device = device;
+ }
+
+ HRESULT BeginDrawing(IAvnMetalRenderingSession **ret) override {
+ if([NSThread isMainThread])
+ {
+ // Flush all existing rendering
+ auto buffer = [_device->queue commandBuffer];
+ [buffer commit];
+ [buffer waitUntilCompleted];
+ _size = PendingSize;
+ _scaling= PendingScaling;
+ CGSize layerSize = {(CGFloat)_size.Width, (CGFloat)_size.Height};
+
+ [_layer setDrawableSize: layerSize];
+ }
+ auto drawable = [_layer nextDrawable];
+ if(drawable == nil)
+ {
+ ret = nil;
+ return E_FAIL;
+ }
+ *ret = new AvnMetalRenderSession(_device, _layer, drawable, _size, _scaling);
+ return 0;
+ }
+};
+
+@implementation MetalRenderTarget
+{
+ ComPtr _device;
+ CAMetalLayer* _layer;
+ ComPtr _target;
+}
+- (MetalRenderTarget *)initWithDevice:(IAvnMetalDevice *)device {
+ _device = dynamic_cast(device);
+ _layer = [CAMetalLayer new];
+ _layer.device = _device->device;
+ _target.setNoAddRef(new AvnMetalRenderTarget(_layer, _device));
+ return self;
+}
+
+
+-(void) getRenderTarget: (IAvnMetalRenderTarget**) ppv
+{
+ *ppv = static_cast(_target.getRetainedReference());
+}
+
+- (void)resize:(AvnPixelSize)size withScale:(float)scale {
+ CGSize layerSize = {(CGFloat)size.Width, (CGFloat)size.Height};
+ _target->PendingScaling = scale;
+ _target->PendingSize = size;
+ [_layer setNeedsDisplay];
+}
+
+- (CALayer *)layer {
+ return _layer;
+}
+@end
+
+
+class AvnMetalDisplay : public ComSingleObject
+{
+public:
+ FORWARD_IUNKNOWN()
+ HRESULT CreateDevice(IAvnMetalDevice **ret) override {
+
+ auto device = MTLCreateSystemDefaultDevice();
+ if(device == nil) {
+ ret = nil;
+ return E_FAIL;
+ }
+ auto queue = [device newCommandQueue];
+ *ret = new AvnMetalDevice(device, queue);
+ return S_OK;
+ }
+};
+
+static AvnMetalDisplay* _display = new AvnMetalDisplay();
+
+extern IAvnMetalDisplay* GetMetalDisplay()
+{
+ return _display;
+}
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/noarc.mm b/native/Avalonia.Native/src/OSX/noarc.mm
new file mode 100644
index 0000000000..82378ce84c
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/noarc.mm
@@ -0,0 +1,11 @@
+#include "noarc.h"
+
+CppAutoreleasePool::CppAutoreleasePool()
+{
+ _pool = [[NSAutoreleasePool alloc] init];
+}
+
+CppAutoreleasePool::~CppAutoreleasePool() {
+ auto ptr = (NSAutoreleasePool*)_pool;
+ [ptr release];
+}
diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm
index 1c22c91207..b44a605803 100644
--- a/native/Avalonia.Native/src/OSX/rendertarget.mm
+++ b/native/Avalonia.Native/src/OSX/rendertarget.mm
@@ -5,15 +5,12 @@
#include
#include
+#include
-@interface IOSurfaceHolder : NSObject
-@end
-
-@implementation IOSurfaceHolder
+@implementation IOSurfaceHolder : NSObject
{
@public IOSurfaceRef surface;
@public AvnPixelSize size;
- @public bool hasContent;
@public float scale;
ComPtr _context;
GLuint _framebuffer, _texture, _renderbuffer;
@@ -42,7 +39,6 @@
self->scale = scale;
self->size = size;
self->_context = context;
- self->hasContent = false;
return self;
}
@@ -76,7 +72,7 @@
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0);
}
-
+
if(_renderbuffer == 0)
{
glGenRenderbuffers(1, &_renderbuffer);
@@ -84,48 +80,72 @@
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer);
}
-
+
return S_OK;
}
-(void) finishDraw
{
- ComPtr release;
- _context->MakeCurrent(release.getPPV());
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ if(_framebuffer != 0) {
+ glDeleteFramebuffers(1, &_framebuffer);
+ _framebuffer = 0;
+ }
+ if(_texture != 0) {
+ glDeleteTextures(1, &_texture);
+ _texture = 0;
+ }
+ if(_renderbuffer != 0) {
+ glDeleteRenderbuffers(1, &_renderbuffer);
+ _renderbuffer = 0;
+ }
glFlush();
- self->hasContent = true;
}
-(void) dealloc
{
-
- if(_framebuffer != 0)
- {
- ComPtr release;
- _context->MakeCurrent(release.getPPV());
- glDeleteFramebuffers(1, &_framebuffer);
- if(_texture != 0)
- glDeleteTextures(1, &_texture);
- if(_renderbuffer != 0)
- glDeleteRenderbuffers(1, &_renderbuffer);
- }
-
if(surface != nullptr)
{
CFRelease(surface);
+ surface = nil;
}
}
@end
+
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target);
+static IAvnSoftwareRenderTarget* CreateSoftwareRenderTarget(IOSurfaceRenderTarget* target);
+
+static bool SizeEquals(AvnPixelSize& left, AvnPixelSize& right)
+{
+ return left.Width == right.Width && right.Height == left.Height;
+}
+
+class ConsumeSurfacesCallback : public ComSingleObject
+{
+ IOSurfaceRenderTarget* _target;
+public:
+ FORWARD_IUNKNOWN()
+ ConsumeSurfacesCallback(IOSurfaceRenderTarget* target)
+ {
+ _target = target;
+ }
+
+ void Run() override {
+ [_target consumeSurfaces];
+ }
+};
@implementation IOSurfaceRenderTarget
{
CALayer* _layer;
- @public IOSurfaceHolder* surface;
@public NSObject* lock;
ComPtr _glContext;
+ bool _consumeSurfacesScheduled;
+ std::queue> _surfaces;
+ IOSurfaceHolder* _activeSurface;
+ AvnPixelSize _size;
+ float _scale;
}
- (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
@@ -133,16 +153,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
self = [super init];
_glContext = context;
lock = [NSObject new];
- surface = nil;
+ _layer = [CALayer new];
[self resize:{1,1} withScale: 1];
return self;
}
-- (AvnPixelSize) pixelSize {
- return {1, 1};
-}
-
- (CALayer *)layer {
return _layer;
}
@@ -155,57 +171,110 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
size.Width = 1;
@synchronized (lock) {
- if(surface == nil
- || surface->size.Width != size.Width
- || surface->size.Height != size.Height
- || surface->scale != scale)
- {
- surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()];
-
- [self updateLayer];
- }
+ _size = size;
+ _scale = scale;
}
}
-- (void)updateLayer {
- if ([NSThread isMainThread])
+- (void)setSurfaceInUiThreadContext: (IOSurfaceHolder*) surface
+{
+ [CATransaction begin];
+ if(surface == _activeSurface)
+ [_layer setContents: nil];
+ _activeSurface = surface;
+ [_layer setContentsScale: _activeSurface->scale];
+ [_layer setContents: (__bridge IOSurface*) _activeSurface->surface];
+ [CATransaction commit];
+}
+
+- (void)consumeSurfaces {
+ @synchronized (lock) {
+ _consumeSurfacesScheduled = false;
+
+ while(_surfaces.size() > 1)
+ _surfaces.pop();
+
+ if(_surfaces.size() == 0)
+ return;
+
+ auto targetSurface = _surfaces.front();
+ _surfaces.pop();
+
+ [self setSurfaceInUiThreadContext: targetSurface];
+ }
+ // This can trigger event processing on the main thread
+ // which might need to lock the renderer
+ // which can cause a deadlock. So flush call is outside of the lock
+ [CATransaction flush];
+
+}
+
+- (IOSurfaceHolder*) getNextSurfaceInSafeContext
+{
+ IOSurfaceHolder* targetSurface = nil;
+ if([NSThread isMainThread])
{
- @synchronized (lock) {
- if(_layer == nil)
- return;
- if(!surface->hasContent)
- return;
- [CATransaction begin];
- [_layer setContents: nil];
- if(surface != nil)
+ // Drain the surface queue and try to find a surface usable for rendering
+ while(_surfaces.size() > 0)
+ {
+ auto front = _surfaces.front();
+ _surfaces.pop();
+ if(targetSurface == nil && SizeEquals(front.Value->size, _size))
{
- [_layer setContentsScale: surface->scale];
- [_layer setContents: (__bridge IOSurface*) surface->surface];
+ targetSurface = front;
}
- [CATransaction commit];
}
- // This can trigger event processing on the main thread
- // which might need to lock the renderer
- // which can cause a deadlock. So flush call is outside of the lock
- [CATransaction flush];
+ if(targetSurface == nil && _activeSurface != nil && SizeEquals(_activeSurface->size, _size))
+ targetSurface = _activeSurface;
+ }
+ else
+ {
+ // Try to reuse an outdated surface that is still not picked up by the UI thread
+ while(_surfaces.size() > 1)
+ {
+ auto front = _surfaces.front();
+ _surfaces.pop();
+
+ // Simply discard the surface on size mismatch
+ if(SizeEquals(front.Value->size, _size))
+ targetSurface = front;
+ }
}
+
+ if(targetSurface == nil)
+ targetSurface = [[IOSurfaceHolder alloc] initWithSize: _size withScale: _scale withOpenGlContext: _glContext];
+ return targetSurface;
+}
+
+- (void) presentSurfaceInSafeContext: (IOSurfaceHolder*) surface
+{
+ if([NSThread isMainThread])
+ [self setSurfaceInUiThreadContext: surface];
else
- dispatch_async(dispatch_get_main_queue(), ^{
- [self updateLayer];
- });
+ {
+ _surfaces.push(surface);
+ if(_consumeSurfacesScheduled)
+ return;
+ _consumeSurfacesScheduled = true;
+ __block auto strongSelf = self;
+ ComPtr cb(new ConsumeSurfacesCallback(self), true);
+ PostDispatcherCallback(cb);
+ }
}
-- (void) setNewLayer:(CALayer *)layer {
- _layer = layer;
- [self updateLayer];
+- (void) presentSurface: (IOSurfaceHolder*) surface
+{
+ @synchronized(lock)
+ {
+ [self presentSurfaceInSafeContext: surface];
+ }
}
- (HRESULT)setSwFrame:(AvnFramebuffer *)fb {
@synchronized (lock) {
if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565)
return E_INVALIDARG;
- if(surface == nil)
- return E_FAIL;
+ auto surface = [self getNextSurfaceInSafeContext];
IOSurfaceRef surf = surface->surface;
if(IOSurfaceLock(surf, 0, nil))
return E_FAIL;
@@ -221,8 +290,7 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes);
}
IOSurfaceUnlock(surf, 0, nil);
- surface->hasContent = true;
- [self updateLayer];
+ [self presentSurfaceInSafeContext: surface];
return S_OK;
}
}
@@ -232,6 +300,11 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
return CreateGlRenderTarget(self);
}
+-(IAvnSoftwareRenderTarget*) createSoftwareRenderTarget
+{
+ return CreateSoftwareRenderTarget(self);
+}
+
@end
class AvnGlRenderingSession : public ComSingleObject
@@ -241,12 +314,12 @@ class AvnGlRenderingSession : public ComSingleObject releaseContext)
+ AvnGlRenderingSession(IOSurfaceRenderTarget* target, IOSurfaceHolder* surface, ComPtr releaseContext)
{
_target = target;
// This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this
// particular render session
- _surface = _target->surface;
+ _surface = surface;
_releaseContext = releaseContext;
}
@@ -272,8 +345,9 @@ public:
virtual ~AvnGlRenderingSession()
{
+ START_ARP_CALL;
[_surface finishDraw];
- [_target updateLayer];
+ [_target presentSurface: _surface];
_releaseContext = nil;
}
};
@@ -290,17 +364,15 @@ public:
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
{
- START_COM_CALL;
-
+ START_COM_ARP_CALL;
ComPtr releaseContext;
@synchronized (_target->lock) {
- if(_target->surface == nil)
- return E_FAIL;
+ auto surface = [_target getNextSurfaceInSafeContext];
_target->_glContext->MakeCurrent(releaseContext.getPPV());
- HRESULT res = [_target->surface prepareForGlRender];
+ HRESULT res = [surface prepareForGlRender];
if(res)
return res;
- *ret = new AvnGlRenderingSession(_target, releaseContext);
+ *ret = new AvnGlRenderingSession(_target, surface, releaseContext);
return S_OK;
}
}
@@ -310,4 +382,27 @@ public:
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target)
{
return new AvnGlRenderTarget(target);
-}
+};
+
+
+class AvnSoftwareRenderTarget : public ComSingleObject
+{
+ IOSurfaceRenderTarget* _target;
+public:
+ FORWARD_IUNKNOWN()
+
+ AvnSoftwareRenderTarget(IOSurfaceRenderTarget *target) {
+ _target = target;
+ }
+
+ HRESULT SetFrame(AvnFramebuffer *fb) override {
+ START_COM_ARP_CALL;
+ [_target setSwFrame: fb];
+ return 0;
+ }
+};
+
+static IAvnSoftwareRenderTarget* CreateSoftwareRenderTarget(IOSurfaceRenderTarget* target)
+{
+ return new AvnSoftwareRenderTarget(target);
+};
diff --git a/nukebuild/ApiDiffValidation.cs b/nukebuild/ApiDiffValidation.cs
new file mode 100644
index 0000000000..483bfd336a
--- /dev/null
+++ b/nukebuild/ApiDiffValidation.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Nuke.Common.Tooling;
+
+public static class ApiDiffValidation
+{
+ private static readonly HttpClient s_httpClient = new();
+
+ public static async Task ValidatePackage(
+ Tool apiCompatTool, string packagePath, string baselineVersion,
+ string suppressionFilesFolder, bool updateSuppressionFile)
+ {
+ if (baselineVersion is null)
+ {
+ throw new InvalidOperationException(
+ "Build \"api-baseline\" parameter must be set when running Nuke CreatePackages");
+ }
+
+ if (!Directory.Exists(suppressionFilesFolder))
+ {
+ Directory.CreateDirectory(suppressionFilesFolder!);
+ }
+
+ await using (var baselineStream = await DownloadBaselinePackage(packagePath, baselineVersion))
+ using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read))
+ using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read))
+ using (Helpers.UseTempDir(out var tempFolder))
+ {
+ var targetDlls = GetDlls(target);
+ var baselineDlls = GetDlls(baseline);
+
+ var left = new List();
+ var right = new List();
+
+ var packageId = GetPackageId(packagePath);
+ var suppressionFile = Path.Combine(suppressionFilesFolder, packageId + ".nupkg.xml");
+
+ // Don't use Path.Combine with these left and right tool parameters.
+ // Microsoft.DotNet.ApiCompat.Tool is stupid and treats '/' and '\' as different assemblies in suppression files.
+ // So, always use Unix '/'
+ foreach (var baselineDll in baselineDlls)
+ {
+ var baselineDllPath = $"baseline/{baselineDll.target}/{baselineDll.entry.Name}";
+ var baselineDllRealPath = Path.Combine(tempFolder, baselineDllPath);
+ Directory.CreateDirectory(Path.GetDirectoryName(baselineDllRealPath)!);
+ await using (var baselineDllFile = File.Create(baselineDllRealPath))
+ {
+ await baselineDll.entry.Open().CopyToAsync(baselineDllFile);
+ }
+
+ var targetDll = targetDlls.FirstOrDefault(e =>
+ e.target == baselineDll.target && e.entry.Name == baselineDll.entry.Name);
+ if (targetDll.entry is null)
+ {
+ throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}");
+ }
+
+ var targetDllPath = $"target/{targetDll.target}/{targetDll.entry.Name}";
+ var targetDllRealPath = Path.Combine(tempFolder, targetDllPath);
+ Directory.CreateDirectory(Path.GetDirectoryName(targetDllRealPath)!);
+ await using (var targetDllFile = File.Create(targetDllRealPath))
+ {
+ await targetDll.entry.Open().CopyToAsync(targetDllFile);
+ }
+
+ left.Add(baselineDllPath);
+ right.Add(targetDllPath);
+ }
+
+ if (left.Any())
+ {
+ var args = $""" -l={string.Join(',', left)} -r="{string.Join(',', right)}" """;
+ if (File.Exists(suppressionFile))
+ {
+ args += $""" --suppression-file="{suppressionFile}" """;
+ }
+
+ if (updateSuppressionFile)
+ {
+ args += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file=true """;
+ }
+
+ var result = apiCompatTool(args, tempFolder)
+ .Where(t => t.Type == OutputType.Err).ToArray();
+ if (result.Any())
+ {
+ throw new AggregateException(
+ $"ApiDiffValidation task has failed for \"{Path.GetFileName(packagePath)}\" package",
+ result.Select(r => new Exception(r.Text)));
+ }
+ }
+ }
+ }
+
+ private static IReadOnlyCollection<(string target, ZipArchiveEntry entry)> GetDlls(ZipArchive archive)
+ {
+ return archive.Entries
+ .Where(e => Path.GetExtension(e.FullName) == ".dll"
+ // Exclude analyzers and build task, as we don't care about breaking changes there
+ && !e.FullName.Contains("analyzers/") && !e.FullName.Contains("analyzers\\")
+ && !e.Name.Contains("Avalonia.Build.Tasks"))
+ .Select(e => (
+ entry: e,
+ isRef: e.FullName.Contains("ref/") || e.FullName.Contains("ref\\"),
+ target: Path.GetDirectoryName(e.FullName)!.Split(new [] { '/', '\\' }).Last())
+ )
+ .GroupBy(e => (e.target, e.entry.Name))
+ .Select(g => g.MaxBy(e => e.isRef))
+ .Select(e => (e.target, e.entry))
+ .ToArray();
+ }
+
+ static async Task DownloadBaselinePackage(string packagePath, string baselineVersion)
+ {
+ /*
+ Gets package name from versions like:
+ Avalonia.0.10.0-preview1
+ Avalonia.11.0.999-cibuild0037534-beta
+ Avalonia.11.0.0
+ */
+ var packageId = GetPackageId(packagePath);
+ Build.Information("Downloading {0} {1} baseline package", packageId, baselineVersion);
+
+ try
+ {
+ using var response = await s_httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get,
+ $"https://www.nuget.org/api/v2/package/{packageId}/{baselineVersion}"), HttpCompletionOption.ResponseHeadersRead);
+ response.EnsureSuccessStatusCode();
+
+ await using var stream = await response.Content.ReadAsStreamAsync();
+ var memoryStream = new MemoryStream();
+ await stream.CopyToAsync(memoryStream);
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ return memoryStream;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Downloading baseline package for {packageId} {baselineVersion} failed.\r" + ex.Message, ex);
+ }
+ }
+
+ static string GetPackageId(string packagePath)
+ {
+ return Regex.Replace(
+ Path.GetFileNameWithoutExtension(packagePath),
+ """(\.\d+\.\d+\.\d+(?:-.+)?)$""", "");
+ }
+}
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 524c9fa4e4..f8fbf64e83 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -36,6 +36,10 @@ using MicroCom.CodeGenerator;
partial class Build : NukeBuild
{
BuildParameters Parameters { get; set; }
+
+ [PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net6.0")]
+ Tool ApiCompatTool;
+
protected override void OnBuildInitialized()
{
Parameters = new BuildParameters(this);
@@ -275,10 +279,21 @@ partial class Build : NukeBuild
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
new NumergeNukeLogger()))
throw new Exception("Package merge failed");
- RefAssemblyGenerator.GenerateRefAsmsInPackage(Parameters.NugetRoot / "Avalonia." +
- Parameters.Version + ".nupkg");
+ RefAssemblyGenerator.GenerateRefAsmsInPackage(
+ Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.nupkg",
+ Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.snupkg");
});
-
+
+ Target ValidateApiDiff => _ => _
+ .DependsOn(CreateNugetPackages)
+ .Executes(async () =>
+ {
+ await Task.WhenAll(
+ Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffValidation.ValidatePackage(
+ ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline,
+ Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression)));
+ });
+
Target RunTests => _ => _
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)
@@ -288,7 +303,8 @@ partial class Build : NukeBuild
Target Package => _ => _
.DependsOn(RunTests)
- .DependsOn(CreateNugetPackages);
+ .DependsOn(CreateNugetPackages)
+ .DependsOn(ValidateApiDiff);
Target CiAzureLinux => _ => _
.DependsOn(RunTests);
diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs
index dfa914d1db..897fdc84f1 100644
--- a/nukebuild/BuildParameters.cs
+++ b/nukebuild/BuildParameters.cs
@@ -10,18 +10,24 @@ using static Nuke.Common.IO.PathConstruction;
public partial class Build
{
- [Parameter("configuration")]
+ [Parameter(Name = "configuration")]
public string Configuration { get; set; }
- [Parameter("skip-tests")]
+ [Parameter(Name = "skip-tests")]
public bool SkipTests { get; set; }
- [Parameter("force-nuget-version")]
+ [Parameter(Name = "force-nuget-version")]
public string ForceNugetVersion { get; set; }
- [Parameter("skip-previewer")]
+ [Parameter(Name = "skip-previewer")]
public bool SkipPreviewer { get; set; }
+ [Parameter(Name = "api-baseline")]
+ public string ApiValidationBaseline { get; set; }
+
+ [Parameter(Name = "update-api-suppression")]
+ public bool? UpdateApiValidationSuppression { get; set; }
+
public class BuildParameters
{
public string Configuration { get; }
@@ -57,7 +63,9 @@ public partial class Build
public string FileZipSuffix { get; }
public AbsolutePath ZipCoreArtifacts { get; }
public AbsolutePath ZipNuGetArtifacts { get; }
-
+ public string ApiValidationBaseline { get; }
+ public bool UpdateApiValidationSuppression { get; }
+ public AbsolutePath ApiValidationSuppressionFiles { get; }
public BuildParameters(Build b)
{
@@ -103,6 +111,9 @@ public partial class Build
// VERSION
Version = b.ForceNugetVersion ?? GetVersion();
+ ApiValidationBaseline = b.ApiValidationBaseline ?? new Version(new Version(Version).Major, 0).ToString();
+ UpdateApiValidationSuppression = b.UpdateApiValidationSuppression ?? IsLocalBuild;
+
if (IsRunningOnAzure)
{
if (!IsNuGetRelease)
@@ -125,6 +136,7 @@ public partial class Build
FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix);
+ ApiValidationSuppressionFiles = RootDirectory / "api";
}
string GetVersion()
diff --git a/nukebuild/Numerge b/nukebuild/Numerge
index aef10ae67d..9738c6121f 160000
--- a/nukebuild/Numerge
+++ b/nukebuild/Numerge
@@ -1 +1 @@
-Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
+Subproject commit 9738c6121fdd143c78d3e25686a7e4e13c00f586
diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs
index 54e428c442..e93070e2f0 100644
--- a/nukebuild/RefAssemblyGenerator.cs
+++ b/nukebuild/RefAssemblyGenerator.cs
@@ -1,8 +1,10 @@
+#nullable enable
+
+using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
-using ILRepacking;
using Mono.Cecil;
using Mono.Cecil.Cil;
@@ -10,8 +12,8 @@ public class RefAssemblyGenerator
{
class Resolver : DefaultAssemblyResolver, IAssemblyResolver
{
- private readonly string _dir;
- Dictionary _cache = new();
+ readonly string _dir;
+ readonly Dictionary _cache = new();
public Resolver(string dir)
{
@@ -31,17 +33,17 @@ public class RefAssemblyGenerator
public static void PatchRefAssembly(string file)
{
- var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk");
+ var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk")!;
var snk = new byte[reader.Length];
- reader.Read(snk, 0, snk.Length);
+ reader.ReadExactly(snk, 0, snk.Length);
var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters
{
ReadWrite = true,
InMemory = true,
ReadSymbols = true,
- SymbolReaderProvider = new DefaultSymbolReaderProvider(false),
- AssemblyResolver = new Resolver(Path.GetDirectoryName(file))
+ SymbolReaderProvider = new DefaultSymbolReaderProvider(throwIfNoSymbol: true),
+ AssemblyResolver = new Resolver(Path.GetDirectoryName(file)!)
});
var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule,
@@ -58,7 +60,7 @@ public class RefAssemblyGenerator
{
StrongNameKeyBlob = snk,
WriteSymbols = def.MainModule.HasSymbols,
- SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(),
+ SymbolWriterProvider = new PortablePdbWriterProvider(),
DeterministicMvid = def.MainModule.HasSymbols
});
}
@@ -146,7 +148,7 @@ public class RefAssemblyGenerator
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
}
- static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute)
+ static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute? unstableAttribute)
{
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
return;
@@ -172,43 +174,66 @@ public class RefAssemblyGenerator
});
}
- public static void GenerateRefAsmsInPackage(string packagePath)
+ public static void GenerateRefAsmsInPackage(string mainPackagePath, string symbolsPackagePath)
{
- using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
- ZipArchiveMode.Update))
+ using var mainArchive = OpenPackage(mainPackagePath);
+ using var symbolsArchive = OpenPackage(symbolsPackagePath);
+
+ foreach (var entry in mainArchive.Entries
+ .Where(e => e.FullName.StartsWith("ref/", StringComparison.Ordinal))
+ .ToArray())
{
- foreach (var entry in archive.Entries.ToList())
- {
- if (entry.FullName.StartsWith("ref/"))
- entry.Delete();
- }
-
- foreach (var entry in archive.Entries.ToList())
+ entry.Delete();
+ }
+
+ foreach (var libEntry in GetLibEntries(mainArchive, ".xml"))
+ {
+ var refEntry = mainArchive.CreateEntry("ref/" + libEntry.FullName.Substring(4), CompressionLevel.Optimal);
+ using var src = libEntry.Open();
+ using var dst = refEntry.Open();
+ src.CopyTo(dst);
+ }
+
+ var pdbEntries = GetLibEntries(symbolsArchive, ".pdb").ToDictionary(e => e.FullName);
+
+ var libs = GetLibEntries(mainArchive, ".dll")
+ .Select(e => (NameParts: e.FullName.Split('/'), Entry: e))
+ .Select(e => (
+ Tfm: e.NameParts[1],
+ DllName: e.NameParts[2],
+ DllEntry: e.Entry,
+ PdbName: Path.ChangeExtension(e.NameParts[2], ".pdb"),
+ PdbEntry: pdbEntries.TryGetValue(Path.ChangeExtension(e.Entry.FullName, ".pdb"), out var pdbEntry) ?
+ pdbEntry :
+ throw new InvalidOperationException($"Missing symbols for {e.Entry.FullName}")))
+ .GroupBy(e => e.Tfm);
+
+ foreach (var tfm in libs)
+ {
+ using var _ = Helpers.UseTempDir(out var temp);
+
+ foreach (var lib in tfm)
{
- if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml"))
- {
- var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4),
- CompressionLevel.Optimal);
- using (var src = entry.Open())
- using (var dst = newEntry.Open())
- src.CopyTo(dst);
- }
- }
+ var extractedDllPath = Path.Combine(temp, lib.DllName);
+ var extractedPdbPath = Path.Combine(temp, lib.PdbName);
+
+ lib.DllEntry.ExtractToFile(extractedDllPath);
+ lib.PdbEntry.ExtractToFile(extractedPdbPath);
- var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll"))
- .Select((e => new { s = e.FullName.Split('/'), e = e }))
- .Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e })
- .GroupBy(x => x.Tfm);
- foreach(var tfm in libs)
- using (Helpers.UseTempDir(out var temp))
- {
- foreach (var l in tfm)
- l.Entry.ExtractToFile(Path.Combine(temp, l.Name));
- foreach (var l in tfm)
- PatchRefAssembly(Path.Combine(temp, l.Name));
- foreach (var l in tfm)
- archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}");
- }
+ PatchRefAssembly(extractedDllPath);
+
+ mainArchive.CreateEntryFromFile(extractedDllPath, $"ref/{lib.Tfm}/{lib.DllName}");
+ symbolsArchive.CreateEntryFromFile(extractedPdbPath, $"ref/{lib.Tfm}/{lib.PdbName}");
+ }
}
+
+ static ZipArchive OpenPackage(string packagePath)
+ => new(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite), ZipArchiveMode.Update);
+
+ static ZipArchiveEntry[] GetLibEntries(ZipArchive archive, string extension)
+ => archive.Entries
+ .Where(e => e.FullName.StartsWith("lib/", StringComparison.Ordinal)
+ && e.FullName.EndsWith(extension, StringComparison.Ordinal))
+ .ToArray();
}
}
diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs
index 6f79972ad6..eecfcf6da1 100644
--- a/nukebuild/Shims.cs
+++ b/nukebuild/Shims.cs
@@ -9,12 +9,12 @@ using Numerge;
public partial class Build
{
- static void Information(string info)
+ internal static void Information(string info)
{
Logger.Info(info);
}
- static void Information(string info, params object[] args)
+ internal static void Information(string info, params object[] args)
{
Logger.Info(info, args);
}
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index 8999b7ca76..64eccbbb3f 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -8,8 +8,8 @@
1
net7.0
-
-
+
+
@@ -22,6 +22,8 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
@@ -30,12 +32,16 @@
-
-
-
+
+
+
-
-
+
+
+
+ dirs.proj
+
+
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 8f1b39ae12..ebfa325067 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -5,7 +5,7 @@
-
+
all
diff --git a/packages/Avalonia/Avalonia.targets b/packages/Avalonia/Avalonia.targets
index 50306f2cdc..eacd287b59 100644
--- a/packages/Avalonia/Avalonia.targets
+++ b/packages/Avalonia/Avalonia.targets
@@ -1,3 +1,4 @@
+
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 33f22f4d02..6af35ff0d2 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -144,4 +144,30 @@
+
+
+ Build
+
+
+
+
+ http://127.0.0.1:6001
+ $(OutputPath)/$(AssemblyName).dll
+ MainWindow.axaml
+ $(APreviewExecutable)
+ $([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.deps.json'))
+ $([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.runtimeconfig.json'))
+ $([System.IO.Path]::GetFullPath('$(APreviewFile)'))
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets
new file mode 100644
index 0000000000..5343559c07
--- /dev/null
+++ b/packages/Avalonia/AvaloniaPrivateApis.targets
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ net6.0
+ netstandard2.0
+
+
+
+
+
+
+
+
+
diff --git a/readme.md b/readme.md
index e9cd220714..fc5e218a92 100644
--- a/readme.md
+++ b/readme.md
@@ -6,18 +6,16 @@
[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia) 
-⚠️ **v11 Update - [Pausing community contributions](https://github.com/AvaloniaUI/Avalonia/discussions/10599)**
-
## 📖 About
[Avalonia](https://avaloniaui.net) is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of platforms such as Windows, macOS, Linux, iOS, Android and WebAssembly. Avalonia is mature and production ready and is used by companies, including [Schneider Electric](https://avaloniaui.net/showcase#se), [Unity](https://avaloniaui.net/showcase#unity), [JetBrains](https://avaloniaui.net/showcase#rider) and [Github](https://avaloniaui.net/showcase#github).
-Considered by many to be the spiritual successor to WPF, Avalonia UI provides a familiar, modern development experience for XAML developers creating cross-platform applications. While Avalonia UI is [similar to WPF](https://docs.avaloniaui.net/misc/wpf), it isn't a 1:1 copy, and you'll find plenty of improvements.
+Considered by many to be the spiritual successor to WPF, Avalonia UI provides a familiar, modern development experience for XAML developers creating cross-platform applications. While Avalonia UI is [similar to WPF](https://docs.avaloniaui.net/docs/next/get-started/wpf/), it isn't a 1:1 copy, and you'll find plenty of improvements.
For those seeking a cross-platform WPF, we have created [Avalonia XPF](https://avaloniaui.net/xpf), enabling WPF applications to run on macOS and Linux with little to no code changes. Avalonia XPF is a commercial product and is licensed per-app, per-platform.
#### Roadmap
-To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239).
+To see the status of some of our features, look at our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239).
#### Breaking Changes
You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been.
diff --git a/samples/AppWithoutLifetime/MainWindow.axaml.cs b/samples/AppWithoutLifetime/MainWindow.axaml.cs
index d27d5d5653..df3ed3cea0 100644
--- a/samples/AppWithoutLifetime/MainWindow.axaml.cs
+++ b/samples/AppWithoutLifetime/MainWindow.axaml.cs
@@ -17,10 +17,11 @@ public partial class MainWindow : Window
AvaloniaXamlLoader.Load(this);
}
- protected override void OnLoaded()
+ ///
+ protected override void OnLoaded(RoutedEventArgs e)
{
this.AttachDevTools();
- base.OnLoaded();
+ base.OnLoaded(e);
}
public void Open(object sender, RoutedEventArgs e)
diff --git a/samples/AppWithoutLifetime/Sub.axaml.cs b/samples/AppWithoutLifetime/Sub.axaml.cs
index 50c770b3a2..3a7ce787bc 100644
--- a/samples/AppWithoutLifetime/Sub.axaml.cs
+++ b/samples/AppWithoutLifetime/Sub.axaml.cs
@@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace AppWithoutLifetime;
@@ -16,9 +17,10 @@ public partial class Sub : Window
AvaloniaXamlLoader.Load(this);
}
- protected override void OnLoaded()
+ ///
+ protected override void OnLoaded(RoutedEventArgs e)
{
this.AttachDevTools();
- base.OnLoaded();
+ base.OnLoaded(e);
}
}
diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index a43ea4539a..f668067f80 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -10,6 +10,11 @@
apk
true
+
+
+
+
+
Resources\drawable\Icon.png
@@ -37,6 +42,10 @@
+
+
+
+
diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs
index 486d14661e..9b5b06e5fb 100644
--- a/samples/ControlCatalog.Android/MainActivity.cs
+++ b/samples/ControlCatalog.Android/MainActivity.cs
@@ -5,8 +5,16 @@ using Avalonia.Android;
namespace ControlCatalog.Android
{
- [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
- public class MainActivity : AvaloniaMainActivity
+ [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
+ public class MainActivity : AvaloniaMainActivity
{
+ protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
+ {
+ return base.CustomizeAppBuilder(builder)
+ .AfterSetup(_ =>
+ {
+ Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
+ });
+ }
}
}
diff --git a/samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml b/samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml
new file mode 100644
index 0000000000..dde4b5a7dd
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml b/samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml
new file mode 100644
index 0000000000..94f27d9e63
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Android/Resources/values-v31/styles.xml b/samples/ControlCatalog.Android/Resources/values-v31/styles.xml
new file mode 100644
index 0000000000..d5ecec4391
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/values-v31/styles.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Android/Resources/values/styles.xml b/samples/ControlCatalog.Android/Resources/values/styles.xml
index 3e1270256d..6e534de2f0 100644
--- a/samples/ControlCatalog.Android/Resources/values/styles.xml
+++ b/samples/ControlCatalog.Android/Resources/values/styles.xml
@@ -6,16 +6,7 @@
-
-
-
-
-
diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs
deleted file mode 100644
index a0b68b129b..0000000000
--- a/samples/ControlCatalog.Android/SplashActivity.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Android.App;
-using Android.Content;
-using Android.Content.PM;
-using Android.OS;
-using Avalonia.Android;
-
-namespace ControlCatalog.Android
-{
- [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
- public class SplashActivity : AvaloniaSplashActivity
- {
- protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
- {
- return base.CustomizeAppBuilder(builder)
- .AfterSetup(_ =>
- {
- Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
- });
- }
-
- protected override void OnCreate(Bundle? savedInstanceState)
- {
- base.OnCreate(savedInstanceState);
- }
-
- protected override void OnResume()
- {
- base.OnResume();
-
- StartActivity(new Intent(Application.Context, typeof(MainActivity)));
-
- Finish();
- }
- }
-}
diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
index c6405dabb6..2c47effa38 100644
--- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
+++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
@@ -4,10 +4,17 @@
Exe
net461
x64
+ true
+ ../ControlCatalog.NetCore/app.manifest
+
+
+
+
+
diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs
index 4d28f15e2c..eeb2095201 100644
--- a/samples/ControlCatalog.Desktop/Program.cs
+++ b/samples/ControlCatalog.Desktop/Program.cs
@@ -3,6 +3,8 @@ using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform;
+using ControlCatalog.NetCore;
+using ControlCatalog.Pages;
namespace ControlCatalog
{
@@ -18,6 +20,15 @@ namespace ControlCatalog
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.LogToTrace()
+ .AfterSetup(builder =>
+ {
+ builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ {
+ StartupScreenIndex = 1,
+ });
+
+ EmbedSample.Implementation = new EmbedSampleWin();
+ })
.UsePlatformDetect();
private static void ConfigureAssetAssembly(AppBuilder builder)
diff --git a/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs b/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
index 47d368f7a4..bd0c1bf98e 100644
--- a/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
+++ b/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
@@ -39,7 +39,7 @@ internal unsafe class WinApi
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
- [DllImport("kernel32.dll")]
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)]
public static extern IntPtr LoadLibrary(string lib);
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index 85c159467b..5e3e301461 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -18,9 +18,16 @@ namespace ControlCatalog.NetCore
{
static class Program
{
+ private static bool s_useFramebuffer;
+
[STAThread]
static int Main(string[] args)
{
+ if (args.Contains("--fbdev"))
+ {
+ s_useFramebuffer = true;
+ }
+
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");
@@ -42,10 +49,10 @@ namespace ControlCatalog.NetCore
return scaling;
return 1;
}
- if (args.Contains("--fbdev"))
+ if (s_useFramebuffer)
{
- SilenceConsole();
- return builder.StartLinuxFbDev(args, scaling: GetScaling());
+ SilenceConsole();
+ return builder.StartLinuxFbDev(args, scaling: GetScaling());
}
else if (args.Contains("--vnc"))
{
@@ -103,8 +110,7 @@ namespace ControlCatalog.NetCore
{
builder.With(new Win32PlatformOptions()
{
- UseLowLatencyDxgiSwapChain = true,
- UseWindowsUIComposition = false
+ CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain }
});
return builder.StartWithClassicDesktopLifetime(args);
}
@@ -128,10 +134,13 @@ namespace ControlCatalog.NetCore
.WithInterFont()
.AfterSetup(builder =>
{
- builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ if (!s_useFramebuffer)
{
- StartupScreenIndex = 1,
- });
+ builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ {
+ StartupScreenIndex = 1,
+ });
+ }
EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
: OperatingSystem.IsMacOS() ? new EmbedSampleMac()
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index 64bf3e53b3..02b1242471 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -28,6 +28,9 @@
+
+
+
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 246fe4385f..605254c995 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -30,8 +30,8 @@ namespace ControlCatalog
AvaloniaXamlLoader.Load(this);
- _fluentTheme = new FluentTheme();
- _simpleTheme = new SimpleTheme();
+ _fluentTheme = (FluentTheme)Resources["FluentTheme"]!;
+ _simpleTheme = (SimpleTheme)Resources["SimpleTheme"]!;
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
diff --git a/samples/ControlCatalog/Models/GDPdLengthConverter.cs b/samples/ControlCatalog/Models/GDPdLengthConverter.cs
new file mode 100644
index 0000000000..034e664305
--- /dev/null
+++ b/samples/ControlCatalog/Models/GDPdLengthConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Globalization;
+using Avalonia.Data.Converters;
+
+namespace ControlCatalog.Models;
+
+internal class GDPdLengthConverter : IValueConverter
+{
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is double d)
+ {
+ return new Avalonia.Controls.DataGridLength(d,Avalonia.Controls.DataGridLengthUnitType.Pixel,d,d);
+ }
+ else if (value is decimal d2)
+ {
+ var dv =System.Convert.ToDouble(d2);
+ return new Avalonia.Controls.DataGridLength(dv, Avalonia.Controls.DataGridLengthUnitType.Pixel, dv, dv);
+ }
+ return value;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is Avalonia.Controls.DataGridLength width)
+ {
+ return System.Convert.ToDecimal(width.DisplayValue);
+ }
+ return value;
+ }
+}
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
index a492808f1d..b682ebf51d 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
@@ -18,6 +18,10 @@
+
+
+
+
@@ -42,7 +46,6 @@
-
diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs
index 0d3061f361..5bf46510dc 100644
--- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs
+++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs
@@ -144,8 +144,8 @@ public partial class CompositionPage : UserControl
{
if(_solidVisual == null)
return;
- _solidVisual.Size = new Vector2((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3);
- _solidVisual.Offset = new Vector3((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3, 0);
+ _solidVisual.Size = new (v.Bounds.Width / 3, v.Bounds.Height / 3);
+ _solidVisual.Offset = new (v.Bounds.Width / 3, v.Bounds.Height / 3, 0);
}
v.AttachedToVisualTree += delegate
{
@@ -164,7 +164,7 @@ public partial class CompositionPage : UserControl
animation.Direction = PlaybackDirection.Alternate;
_solidVisual.StartAnimation("Color", animation);
- _solidVisual.AnchorPoint = new Vector2(0, 0);
+ _solidVisual.AnchorPoint = new (0, 0);
var scale = _solidVisual.Compositor.CreateVector3KeyFrameAnimation();
scale.Duration = TimeSpan.FromSeconds(5);
@@ -195,8 +195,8 @@ public partial class CompositionPage : UserControl
if (_customVisual == null)
return;
var h = (float)Math.Min(v.Bounds.Height, v.Bounds.Width / 3);
- _customVisual.Size = new Vector2((float)v.Bounds.Width, h);
- _customVisual.Offset = new Vector3(0, (float)(v.Bounds.Height - h) / 2, 0);
+ _customVisual.Size = new (v.Bounds.Width, h);
+ _customVisual.Offset = new (0, (v.Bounds.Height - h) / 2, 0);
}
v.AttachedToVisualTree += delegate
{
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index fb097da71b..88252091c4 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -1,11 +1,14 @@
+
+
@@ -28,19 +31,31 @@
-
+
+
+
+
+
-
+
-
@@ -68,7 +83,7 @@
-
+
diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs
index cc4429f414..c81ba4fb17 100644
--- a/samples/ControlCatalog/Pages/GesturePage.cs
+++ b/samples/ControlCatalog/Pages/GesturePage.cs
@@ -13,7 +13,7 @@ namespace ControlCatalog.Pages
public class GesturePage : UserControl
{
private bool _isInit;
- private float _currentScale;
+ private double _currentScale;
public GesturePage()
{
@@ -53,7 +53,7 @@ namespace ControlCatalog.Pages
if(compositionVisual!= null)
{
_currentScale = 1;
- compositionVisual.Scale = new Vector3(1,1,1);
+ compositionVisual.Scale = new (1,1,1);
compositionVisual.Offset = default;
image.InvalidateMeasure();
}
@@ -69,7 +69,7 @@ namespace ControlCatalog.Pages
}
_currentScale = 1;
- Vector3 currentOffset = default;
+ Vector3D currentOffset = default;
CompositionVisual? compositionVisual = null;
@@ -133,11 +133,11 @@ namespace ControlCatalog.Pages
if (compositionVisual != null && _currentScale != 1)
{
- currentOffset += new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
+ currentOffset += new Vector3D(e.Delta.X, e.Delta.Y, 0);
var currentSize = control.Bounds.Size * _currentScale;
- currentOffset = new Vector3((float)MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
+ currentOffset = new Vector3D(MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
(float)MathUtilities.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
0);
@@ -157,7 +157,7 @@ namespace ControlCatalog.Pages
var ball = control.FindLogicalDescendantOfType();
- Vector3 defaultOffset = default;
+ Vector3D defaultOffset = default;
CompositionVisual? ballCompositionVisual = null;
@@ -181,11 +181,11 @@ namespace ControlCatalog.Pages
control.AddHandler(Gestures.PullGestureEvent, (s, e) =>
{
- Vector3 center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0);
+ Vector3D center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0);
InitComposition(ball!);
if (ballCompositionVisual != null)
{
- ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
+ ballCompositionVisual.Offset = defaultOffset + new Vector3D(e.Delta.X * 0.4f, e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
e.Handled = true;
}
diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
index 7f32536b11..9c30992624 100644
--- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
@@ -21,7 +21,7 @@ namespace ControlCatalog.ViewModels
public ListBoxPageViewModel()
{
Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
-
+
Selection = new SelectionModel();
Selection.Select(1);
@@ -34,7 +34,13 @@ namespace ControlCatalog.ViewModels
(t ? Avalonia.Controls.SelectionMode.Toggle : 0) |
(a ? Avalonia.Controls.SelectionMode.AlwaysSelected : 0));
- AddItemCommand = MiniCommand.Create(() => Items.Add(GenerateItem()));
+ AddItemCommand = MiniCommand.Create(() =>
+ {
+ var item = GenerateItem();
+ Items.Add(item);
+ Selection.Clear();
+ Selection.Select(Items.Count - 1);
+ });
RemoveItemCommand = MiniCommand.Create(() =>
{
@@ -96,7 +102,7 @@ namespace ControlCatalog.ViewModels
public MiniCommand RemoveItemCommand { get; }
public MiniCommand SelectRandomItemCommand { get; }
- private ItemModel GenerateItem() => new ItemModel(_counter ++);
+ private ItemModel GenerateItem() => new ItemModel(_counter++);
}
///
diff --git a/samples/GpuInterop/DrawingSurfaceDemoBase.cs b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
index aad813ea82..367436e5a5 100644
--- a/samples/GpuInterop/DrawingSurfaceDemoBase.cs
+++ b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
@@ -48,7 +48,7 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
Surface = _compositor.CreateDrawingSurface();
_visual = _compositor.CreateSurfaceVisual();
- _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+ _visual.Size = new (Bounds.Width, Bounds.Height);
_visual.Surface = Surface;
ElementComposition.SetElementChildVisual(this, _visual);
var (res, info) = await DoInitialize(_compositor, Surface);
@@ -72,7 +72,7 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
if (root == null)
return;
- _visual!.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+ _visual!.Size = new (Bounds.Width, Bounds.Height);
var size = PixelSize.FromSize(Bounds.Size, root.RenderScaling);
RenderFrame(size);
if (SupportsDisco && Disco > 0)
diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs
index d65f0dec92..702e9be9e6 100644
--- a/samples/MobileSandbox.Android/MainActivity.cs
+++ b/samples/MobileSandbox.Android/MainActivity.cs
@@ -1,12 +1,14 @@
-using Android.App;
+using System;
+using Android.App;
using Android.Content.PM;
+using Android.OS;
using Avalonia;
using Avalonia.Android;
namespace MobileSandbox.Android
{
- [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
- public class MainActivity : AvaloniaMainActivity
+ [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AvaloniaMainActivity
{
}
}
diff --git a/samples/MobileSandbox.Android/MobileSandbox.Android.csproj b/samples/MobileSandbox.Android/MobileSandbox.Android.csproj
index 94f0d31e4d..3b67d79f7b 100644
--- a/samples/MobileSandbox.Android/MobileSandbox.Android.csproj
+++ b/samples/MobileSandbox.Android/MobileSandbox.Android.csproj
@@ -10,6 +10,12 @@
apk
true
+
+
+
+
+
+
Resources\drawable\Icon.png
@@ -37,6 +43,10 @@
+
+
+
+
diff --git a/samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml b/samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml
new file mode 100644
index 0000000000..dde4b5a7dd
--- /dev/null
+++ b/samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml b/samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml
new file mode 100644
index 0000000000..94f27d9e63
--- /dev/null
+++ b/samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MobileSandbox.Android/Resources/values-night/colors.xml b/samples/MobileSandbox.Android/Resources/values-night/colors.xml
new file mode 100644
index 0000000000..3d47b6fc58
--- /dev/null
+++ b/samples/MobileSandbox.Android/Resources/values-night/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #212121
+
diff --git a/samples/MobileSandbox.Android/Resources/values-v31/styles.xml b/samples/MobileSandbox.Android/Resources/values-v31/styles.xml
new file mode 100644
index 0000000000..7518f078e5
--- /dev/null
+++ b/samples/MobileSandbox.Android/Resources/values-v31/styles.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/samples/MobileSandbox.Android/Resources/values/styles.xml b/samples/MobileSandbox.Android/Resources/values/styles.xml
index 2759d2904a..22085806da 100644
--- a/samples/MobileSandbox.Android/Resources/values/styles.xml
+++ b/samples/MobileSandbox.Android/Resources/values/styles.xml
@@ -6,12 +6,7 @@
-
-
-
diff --git a/samples/MobileSandbox.Android/SplashActivity.cs b/samples/MobileSandbox.Android/SplashActivity.cs
deleted file mode 100644
index ced092554d..0000000000
--- a/samples/MobileSandbox.Android/SplashActivity.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Android.App;
-using Android.Content;
-using Avalonia.Android;
-
-namespace MobileSandbox.Android
-{
- [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
- public class SplashActivity : AvaloniaSplashActivity
- {
- protected override void OnResume()
- {
- base.OnResume();
-
- StartActivity(new Intent(Application.Context, typeof(MainActivity)));
- }
- }
-}
diff --git a/samples/MobileSandbox.Browser/Logo.svg b/samples/MobileSandbox.Browser/Logo.svg
new file mode 100644
index 0000000000..9685a23af1
--- /dev/null
+++ b/samples/MobileSandbox.Browser/Logo.svg
@@ -0,0 +1,5 @@
+
diff --git a/samples/MobileSandbox.Browser/MobileSandbox.Browser.csproj b/samples/MobileSandbox.Browser/MobileSandbox.Browser.csproj
new file mode 100644
index 0000000000..001fe94c7f
--- /dev/null
+++ b/samples/MobileSandbox.Browser/MobileSandbox.Browser.csproj
@@ -0,0 +1,43 @@
+
+
+ net7.0
+ browser-wasm
+ main.js
+ Exe
+ true
+ true
+ true
+ -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0
+
+
+
+ true
+ true
+ full
+ true
+ true
+ -O2
+ -O2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MobileSandbox.Browser/Program.cs b/samples/MobileSandbox.Browser/Program.cs
new file mode 100644
index 0000000000..cdd986d6e8
--- /dev/null
+++ b/samples/MobileSandbox.Browser/Program.cs
@@ -0,0 +1,19 @@
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Browser;
+using MobileSandbox;
+
+[assembly:SupportedOSPlatform("browser")]
+
+internal partial class Program
+{
+ public static async Task Main(string[] args)
+ {
+ await BuildAvaloniaApp()
+ .StartBrowserAppAsync("out");
+ }
+
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure();
+}
diff --git a/samples/MobileSandbox.Browser/Properties/launchSettings.json b/samples/MobileSandbox.Browser/Properties/launchSettings.json
new file mode 100644
index 0000000000..66234a3211
--- /dev/null
+++ b/samples/MobileSandbox.Browser/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "MobileSandbox.Browser": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:65312;http://localhost:65313;"
+ }
+ }
+}
diff --git a/samples/MobileSandbox.Browser/Roots.xml b/samples/MobileSandbox.Browser/Roots.xml
new file mode 100644
index 0000000000..5255ae3ef8
--- /dev/null
+++ b/samples/MobileSandbox.Browser/Roots.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/samples/MobileSandbox.Browser/app.css b/samples/MobileSandbox.Browser/app.css
new file mode 100644
index 0000000000..0e6ab12461
--- /dev/null
+++ b/samples/MobileSandbox.Browser/app.css
@@ -0,0 +1,56 @@
+:root {
+ --sat: env(safe-area-inset-top);
+ --sar: env(safe-area-inset-right);
+ --sab: env(safe-area-inset-bottom);
+ --sal: env(safe-area-inset-left);
+}
+
+#out {
+ height: 100vh;
+ width: 100vw
+}
+
+#avalonia-splash {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ color: whitesmoke;
+ background: #171C2C;
+ font-family: 'Nunito', sans-serif;
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+}
+
+#avalonia-splash a{
+ color: whitesmoke;
+ text-decoration: none;
+}
+
+.center {
+ display: flex;
+ justify-content: center;
+ height: 250px;
+}
+
+.splash-close {
+ animation: slide 0.5s linear 1s forwards;
+}
+
+@keyframes slide {
+ 0% {
+ top: 0%;
+ }
+
+ 50% {
+ opacity: 80%;
+ }
+
+ 100% {
+ top: 100%;
+ overflow: hidden;
+ opacity: 0;
+ display: none;
+ visibility: collapse;
+ }
+}
diff --git a/samples/MobileSandbox.Browser/favicon.ico b/samples/MobileSandbox.Browser/favicon.ico
new file mode 100644
index 0000000000..da8d49ff9b
Binary files /dev/null and b/samples/MobileSandbox.Browser/favicon.ico differ
diff --git a/samples/MobileSandbox.Browser/index.html b/samples/MobileSandbox.Browser/index.html
new file mode 100644
index 0000000000..32ab8628fb
--- /dev/null
+++ b/samples/MobileSandbox.Browser/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ Mobile Sandbox
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MobileSandbox.Browser/main.js b/samples/MobileSandbox.Browser/main.js
new file mode 100644
index 0000000000..9d90db8bd2
--- /dev/null
+++ b/samples/MobileSandbox.Browser/main.js
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { dotnet } from './dotnet.js'
+
+const is_browser = typeof window != "undefined";
+if (!is_browser) throw new Error(`Expected to be running in a browser`);
+
+const dotnetRuntime = await dotnet
+ .withDiagnosticTracing(false)
+ .withApplicationArgumentsFromQuery()
+ .create();
+
+const config = dotnetRuntime.getConfig();
+
+await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);
diff --git a/samples/MobileSandbox.Browser/runtimeconfig.template.json b/samples/MobileSandbox.Browser/runtimeconfig.template.json
new file mode 100644
index 0000000000..8f0557352c
--- /dev/null
+++ b/samples/MobileSandbox.Browser/runtimeconfig.template.json
@@ -0,0 +1,11 @@
+{
+ "wasmHostProperties": {
+ "perHostConfig": [
+ {
+ "name": "browser",
+ "html-path": "index.html",
+ "Host": "browser"
+ }
+ ]
+ }
+}
diff --git a/samples/RenderDemo/Pages/CustomStringAnimator.cs b/samples/RenderDemo/Pages/CustomStringAnimator.cs
index 398319726e..4aea131870 100644
--- a/samples/RenderDemo/Pages/CustomStringAnimator.cs
+++ b/samples/RenderDemo/Pages/CustomStringAnimator.cs
@@ -1,9 +1,10 @@
-using Avalonia.Animation;
+using System;
+using Avalonia.Animation;
using Avalonia.Animation.Animators;
namespace RenderDemo.Pages
{
- public class CustomStringAnimator : CustomAnimatorBase
+ public class CustomStringAnimator : InterpolatingAnimator
{
public override string Interpolate(double progress, string oldValue, string newValue)
{
diff --git a/samples/SafeAreaDemo.Android/MainActivity.cs b/samples/SafeAreaDemo.Android/MainActivity.cs
index b0f0a6e419..1df575eb4d 100644
--- a/samples/SafeAreaDemo.Android/MainActivity.cs
+++ b/samples/SafeAreaDemo.Android/MainActivity.cs
@@ -4,8 +4,8 @@ using Avalonia.Android;
namespace SafeAreaDemo.Android
{
- [Activity(Label = "SafeAreaDemo.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
- public class MainActivity : AvaloniaMainActivity
+ [Activity(Label = "SafeAreaDemo.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
+ public class MainActivity : AvaloniaMainActivity
{
}
}
diff --git a/samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml b/samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml
new file mode 100644
index 0000000000..dde4b5a7dd
--- /dev/null
+++ b/samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml b/samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml
new file mode 100644
index 0000000000..94f27d9e63
--- /dev/null
+++ b/samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/SafeAreaDemo.Android/Resources/values-night/colors.xml b/samples/SafeAreaDemo.Android/Resources/values-night/colors.xml
new file mode 100644
index 0000000000..3d47b6fc58
--- /dev/null
+++ b/samples/SafeAreaDemo.Android/Resources/values-night/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #212121
+
diff --git a/samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml b/samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml
new file mode 100644
index 0000000000..7518f078e5
--- /dev/null
+++ b/samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/samples/SafeAreaDemo.Android/Resources/values/styles.xml b/samples/SafeAreaDemo.Android/Resources/values/styles.xml
index 2759d2904a..22085806da 100644
--- a/samples/SafeAreaDemo.Android/Resources/values/styles.xml
+++ b/samples/SafeAreaDemo.Android/Resources/values/styles.xml
@@ -6,12 +6,7 @@
-
-
-
diff --git a/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj b/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj
index f5d2af79d0..cc182b2b56 100644
--- a/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj
+++ b/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj
@@ -1,4 +1,4 @@
-
+
Exe
net7.0-android
@@ -12,13 +12,14 @@
-
- Resources\drawable\Icon.png
-
+
+
-
-
+
+ Resources\drawable\Icon.png
+
+
diff --git a/samples/SafeAreaDemo.Android/SplashActivity.cs b/samples/SafeAreaDemo.Android/SplashActivity.cs
deleted file mode 100644
index 621ad1c675..0000000000
--- a/samples/SafeAreaDemo.Android/SplashActivity.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Android.App;
-using Android.Content;
-using Android.OS;
-using Avalonia;
-using Avalonia.Android;
-using Application = Android.App.Application;
-
-namespace SafeAreaDemo.Android
-{
- [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
- public class SplashActivity : AvaloniaSplashActivity
- {
- protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
- {
- return base.CustomizeAppBuilder(builder);
- }
-
- protected override void OnCreate(Bundle? savedInstanceState)
- {
- base.OnCreate(savedInstanceState);
- }
-
- protected override void OnResume()
- {
- base.OnResume();
-
- StartActivity(new Intent(Application.Context, typeof(MainActivity)));
- }
- }
-}
diff --git a/samples/SafeAreaDemo/Views/MainView.xaml.cs b/samples/SafeAreaDemo/Views/MainView.xaml.cs
index 2b651225e7..4b8c5e5f15 100644
--- a/samples/SafeAreaDemo/Views/MainView.xaml.cs
+++ b/samples/SafeAreaDemo/Views/MainView.xaml.cs
@@ -1,4 +1,5 @@
using Avalonia.Controls;
+using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using SafeAreaDemo.ViewModels;
@@ -11,9 +12,10 @@ namespace SafeAreaDemo.Views
AvaloniaXamlLoader.Load(this);
}
- protected override void OnLoaded()
+ ///
+ protected override void OnLoaded(RoutedEventArgs e)
{
- base.OnLoaded();
+ base.OnLoaded(e);
var insetsManager = TopLevel.GetTopLevel(this)?.InsetsManager;
if (insetsManager != null && DataContext is MainViewModel viewModel)
diff --git a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs
index b5dccffd4d..d8b0724520 100644
--- a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs
+++ b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs
@@ -1,4 +1,4 @@
-using Avalonia.Win32.Embedding;
+using Avalonia.Win32.Interoperability;
namespace WindowsInteropTest
{
@@ -116,4 +116,4 @@ namespace WindowsInteropTest
private System.Windows.Forms.GroupBox groupBox2;
private WinFormsAvaloniaControlHost avaloniaHost;
}
-}
\ No newline at end of file
+}
diff --git a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs
index e6603a817b..d37ed13559 100644
--- a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs
+++ b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs
@@ -1,35 +1,14 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-using Avalonia.Controls;
-using Avalonia.Rendering;
-using Avalonia.VisualTree;
+using System.Windows.Forms;
using ControlCatalog;
namespace WindowsInteropTest
{
public partial class EmbedToWinFormsDemo : Form
{
- private readonly IRenderer _renderer;
-
public EmbedToWinFormsDemo()
{
InitializeComponent();
avaloniaHost.Content = new MainView();
- _renderer = ((TopLevel)avaloniaHost.Content.GetVisualRoot()).Renderer;
- _renderer.Start();
- }
-
- protected override void OnClosed(EventArgs e)
- {
- _renderer.Stop();
- base.OnClosed(e);
}
}
}
diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
deleted file mode 100644
index 1d8dc32a69..0000000000
--- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
deleted file mode 100644
index e1476106ee..0000000000
--- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Rendering;
-using Avalonia.VisualTree;
-using ControlCatalog;
-using Window = System.Windows.Window;
-
-namespace WindowsInteropTest
-{
- ///
- /// Interaction logic for EmbedToWpfDemo.xaml
- ///
- public partial class EmbedToWpfDemo : Window
- {
- private IRenderer _renderer;
- public EmbedToWpfDemo()
- {
- InitializeComponent();
- var view = new MainView();
- Host.Content = view;
- var tl = (TopLevel)view.GetVisualRoot();
- tl.AttachDevTools();
- _renderer = tl.Renderer;
- _renderer.Start();
- var btn = (Avalonia.Controls.Button) RightBtn.Content;
- btn.Click += delegate
- {
- btn.Content += "!";
- };
-
- }
-
- protected override void OnClosed(EventArgs e)
- {
- _renderer.Stop();
- base.OnClosed(e);
- }
- }
-}
diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs
index c2d30c67bb..4ebb88642b 100644
--- a/samples/interop/WindowsInteropTest/Program.cs
+++ b/samples/interop/WindowsInteropTest/Program.cs
@@ -16,14 +16,9 @@ namespace WindowsInteropTest
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure()
.UseWin32()
- .UseDirect2D1()
- .With(new Win32PlatformOptions
- {
- UseWindowsUIComposition = false,
- ShouldRenderOnUIThread = true // necessary for WPF
- })
+ .UseSkia()
.SetupWithoutStarting();
- System.Windows.Forms.Application.Run(new SelectorForm());
+ System.Windows.Forms.Application.Run(new EmbedToWinFormsDemo());
}
}
}
diff --git a/samples/interop/WindowsInteropTest/SelectorForm.Designer.cs b/samples/interop/WindowsInteropTest/SelectorForm.Designer.cs
deleted file mode 100644
index 7cf402913e..0000000000
--- a/samples/interop/WindowsInteropTest/SelectorForm.Designer.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-namespace WindowsInteropTest
-{
- partial class SelectorForm
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.btnEmbedToWinForms = new System.Windows.Forms.Button();
- this.btnEmbedToWpf = new System.Windows.Forms.Button();
- this.SuspendLayout();
- //
- // btnEmbedToWinForms
- //
- this.btnEmbedToWinForms.Location = new System.Drawing.Point(12, 12);
- this.btnEmbedToWinForms.Name = "btnEmbedToWinForms";
- this.btnEmbedToWinForms.Size = new System.Drawing.Size(201, 86);
- this.btnEmbedToWinForms.TabIndex = 0;
- this.btnEmbedToWinForms.Text = "Embed to WinForms";
- this.btnEmbedToWinForms.UseVisualStyleBackColor = true;
- this.btnEmbedToWinForms.Click += new System.EventHandler(this.btnEmbedToWinForms_Click);
- //
- // btnEmbedToWpf
- //
- this.btnEmbedToWpf.Location = new System.Drawing.Point(219, 12);
- this.btnEmbedToWpf.Name = "btnEmbedToWpf";
- this.btnEmbedToWpf.Size = new System.Drawing.Size(201, 86);
- this.btnEmbedToWpf.TabIndex = 1;
- this.btnEmbedToWpf.Text = "Embed to WPF";
- this.btnEmbedToWpf.UseVisualStyleBackColor = true;
- this.btnEmbedToWpf.Click += new System.EventHandler(this.btnEmbedToWpf_Click);
- //
- // SelectorForm
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(432, 284);
- this.Controls.Add(this.btnEmbedToWpf);
- this.Controls.Add(this.btnEmbedToWinForms);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.MaximizeBox = false;
- this.Name = "SelectorForm";
- this.Text = "Interop";
- this.ResumeLayout(false);
-
- }
-
- #endregion
-
- private System.Windows.Forms.Button btnEmbedToWinForms;
- private System.Windows.Forms.Button btnEmbedToWpf;
- }
-}
-
diff --git a/samples/interop/WindowsInteropTest/SelectorForm.cs b/samples/interop/WindowsInteropTest/SelectorForm.cs
deleted file mode 100644
index 6430694f79..0000000000
--- a/samples/interop/WindowsInteropTest/SelectorForm.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-
-namespace WindowsInteropTest
-{
- public partial class SelectorForm : Form
- {
- public SelectorForm()
- {
- InitializeComponent();
- }
-
- private void btnEmbedToWinForms_Click(object sender, EventArgs e)
- {
- new EmbedToWinFormsDemo().ShowDialog(this);
- }
-
- private void btnEmbedToWpf_Click(object sender, EventArgs e)
- {
- new EmbedToWpfDemo().ShowDialog();
- }
- }
-}
diff --git a/samples/interop/WindowsInteropTest/SelectorForm.resx b/samples/interop/WindowsInteropTest/SelectorForm.resx
deleted file mode 100644
index 1af7de150c..0000000000
--- a/samples/interop/WindowsInteropTest/SelectorForm.resx
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
index 95f77f6df9..22387c5a05 100644
--- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
+++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
@@ -1,15 +1,15 @@
WinExe
- net461
- x64
+ net461;net6.0-windows
true
true
-
+
+
diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs
index 2bf3486ca5..f708d6936c 100644
--- a/src/Android/Avalonia.Android/AndroidInputMethod.cs
+++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs
@@ -1,10 +1,10 @@
using System;
using Android.Content;
using Android.Runtime;
+using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
-using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
namespace Avalonia.Android
@@ -13,11 +13,13 @@ namespace Avalonia.Android
{
public View View { get; }
- public ITextInputMethodClient Client { get; }
+ public TextInputMethodClient Client { get; }
public bool IsActive { get; }
public InputMethodManager IMM { get; }
+
+ void OnBatchEditedEnded();
}
enum CustomImeFlags
@@ -36,7 +38,7 @@ namespace Avalonia.Android
{
private readonly TView _host;
private readonly InputMethodManager _imm;
- private ITextInputMethodClient _client;
+ private TextInputMethodClient _client;
private AvaloniaInputConnection _inputConnection;
public AndroidInputMethod(TView host)
@@ -56,7 +58,7 @@ namespace Avalonia.Android
public bool IsActive => Client != null;
- public ITextInputMethodClient Client => _client;
+ public TextInputMethodClient Client => _client;
public InputMethodManager IMM => _imm;
@@ -65,7 +67,7 @@ namespace Avalonia.Android
}
- public void SetClient(ITextInputMethodClient client)
+ public void SetClient(TextInputMethodClient client)
{
_client = client;
@@ -77,9 +79,24 @@ namespace Avalonia.Android
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
- var surroundingText = Client.SurroundingText;
+ var selection = Client.Selection;
+
+ _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
+
+ var surroundingText = _client.SurroundingText ?? "";
+
+ var extractedText = new ExtractedText
+ {
+ Text = new Java.Lang.String(surroundingText),
+ SelectionStart = selection.Start,
+ SelectionEnd = selection.End,
+ PartialEndOffset = surroundingText.Length
+ };
- _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
+ _imm.UpdateExtractedText(_host, _inputConnection?.ExtractedTextToken ?? 0, extractedText);
+
+ _client.SurroundingTextChanged += _client_SurroundingTextChanged;
+ _client.SelectionChanged += _client_SelectionChanged;
}
else
{
@@ -87,6 +104,92 @@ namespace Avalonia.Android
}
}
+ private void _client_SelectionChanged(object sender, EventArgs e)
+ {
+ if (_inputConnection.IsInBatchEdit)
+ return;
+ OnSelectionChanged();
+ }
+
+ private void OnSelectionChanged()
+ {
+ if (Client is null)
+ {
+ return;
+ }
+
+ var selection = Client.Selection;
+
+ _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
+
+ _inputConnection.SetSelection(selection.Start, selection.End);
+ }
+
+ private void _client_SurroundingTextChanged(object sender, EventArgs e)
+ {
+ if (_inputConnection.IsInBatchEdit)
+ return;
+ OnSurroundingTextChanged();
+ }
+
+ public void OnBatchEditedEnded()
+ {
+ if (_inputConnection.IsInBatchEdit)
+ return;
+
+ OnSurroundingTextChanged();
+ OnSelectionChanged();
+ }
+
+ private void OnSurroundingTextChanged()
+ {
+ if(_client is null)
+ {
+ return;
+ }
+
+ var surroundingText = _client.SurroundingText ?? "";
+ var editableText = _inputConnection.EditableWrapper.ToString();
+
+ if (editableText != surroundingText)
+ {
+ _inputConnection.EditableWrapper.IgnoreChange = true;
+
+ var diff = GetDiff();
+
+ _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff);
+
+ _inputConnection.EditableWrapper.IgnoreChange = false;
+
+ if(diff.index == 0)
+ {
+ var selection = _client.Selection;
+ _client.Selection = new TextSelection(selection.Start, 0);
+ _client.Selection = selection;
+ }
+ }
+
+ (int index, string diff) GetDiff()
+ {
+ int index = 0;
+
+ var longerLength = Math.Max(surroundingText.Length, editableText.Length);
+
+ for (int i = 0; i < longerLength; i++)
+ {
+ if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i])
+ {
+ index = i;
+ break;
+ }
+ }
+
+ var diffString = surroundingText.Substring(index, surroundingText.Length - index);
+
+ return (index, diffString);
+ }
+ }
+
public void SetCursorRect(Rect rect)
{
@@ -134,25 +237,8 @@ namespace Avalonia.Android
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
- _client.TextEditable = _inputConnection.InputEditable;
-
return _inputConnection;
});
}
}
-
- internal readonly record struct ComposingRegion
- {
- private readonly int _start = -1;
- private readonly int _end = -1;
-
- public ComposingRegion(int start, int end)
- {
- _start = start;
- _end = end;
- }
-
- public int Start => _start;
- public int End => _end;
- }
}
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index d5d5f211e9..d8202064e7 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Android;
using Avalonia.Android.Platform;
@@ -18,10 +20,44 @@ namespace Avalonia
public static AppBuilder UseAndroid(this AppBuilder builder)
{
return builder
+ .UseStandardRuntimePlatformSubsystem()
.UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
.UseSkia();
}
}
+
+ ///
+ /// Represents the rendering mode for platform graphics.
+ ///
+ public enum AndroidRenderingMode
+ {
+ ///
+ /// Avalonia is rendered into a framebuffer.
+ ///
+ Software = 1,
+
+ ///
+ /// Enables android EGL rendering.
+ ///
+ Egl = 2
+ }
+
+ public sealed class AndroidPlatformOptions
+ {
+ ///
+ /// Gets or sets Avalonia rendering modes with fallbacks.
+ /// The first element in the array has the highest priority.
+ /// The default value is: , .
+ ///
+ ///
+ /// If application should work on as wide range of devices as possible, at least add as a fallback value.
+ ///
+ /// Thrown if no values were matched.
+ public IReadOnlyList RenderingMode { get; set; } = new[]
+ {
+ AndroidRenderingMode.Egl, AndroidRenderingMode.Software
+ };
+ }
}
namespace Avalonia.Android
@@ -47,18 +83,39 @@ namespace Avalonia.Android
.Bind().ToConstant(new ChoreographerTimer())
.Bind().ToSingleton();
- if (Options.UseGpu)
+ var graphics = InitializeGraphics(Options);
+ if (graphics is not null)
{
- EglPlatformGraphics.TryInitialize();
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(graphics);
}
-
- Compositor = new Compositor(AvaloniaLocator.Current.GetService());
+
+ Compositor = new Compositor(graphics);
}
- }
+
+ private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts)
+ {
+ if (opts.RenderingMode is null || !opts.RenderingMode.Any())
+ {
+ throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} must not be empty or null");
+ }
- public sealed class AndroidPlatformOptions
- {
- public bool UseDeferredRendering { get; set; } = false;
- public bool UseGpu { get; set; } = true;
+ foreach (var renderingMode in opts.RenderingMode)
+ {
+ if (renderingMode == AndroidRenderingMode.Software)
+ {
+ return null;
+ }
+
+ if (renderingMode == AndroidRenderingMode.Egl)
+ {
+ if (EglPlatformGraphics.TryCreate() is { } egl)
+ {
+ return egl;
+ }
+ }
+ }
+
+ throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
+ }
}
}
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
new file mode 100644
index 0000000000..3fcfde3ee4
--- /dev/null
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Android
+{
+ partial class AvaloniaMainActivity where TApp : Application, new()
+ {
+ protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
+
+ private static AppBuilder? s_appBuilder;
+ internal static object ViewContent;
+
+ public object Content
+ {
+ get
+ {
+ return ViewContent;
+ }
+ set
+ {
+ ViewContent = value;
+ if (View != null)
+ View.Content = value;
+ }
+ }
+
+ protected AppBuilder CreateAppBuilder()
+ {
+ var builder = AppBuilder.Configure();
+
+ return CustomizeAppBuilder(builder);
+ }
+
+ private void InitializeApp()
+ {
+ if (s_appBuilder == null)
+ {
+ var builder = CreateAppBuilder();
+
+ builder.SetupWithLifetime(new SingleViewLifetime());
+
+ s_appBuilder = builder;
+ }
+
+ View = new AvaloniaView(this);
+ if (ViewContent != null)
+ {
+ View.Content = ViewContent;
+ }
+
+ if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
+ {
+ lifetime.View = View;
+ }
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
index b2cd150933..499924d8fb 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -11,27 +11,49 @@ using AndroidX.AppCompat.App;
namespace Avalonia.Android
{
- public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
+ public class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
{
- internal static object ViewContent;
-
public Action ActivityResult { get; set; }
public Action RequestPermissionsResult { get; set; }
+
+ public event EventHandler BackRequested;
+
+ public override void OnBackPressed()
+ {
+ var eventArgs = new AndroidBackRequestedEventArgs();
+
+ BackRequested?.Invoke(this, eventArgs);
+
+ if (!eventArgs.Handled)
+ {
+ base.OnBackPressed();
+ }
+ }
+
+ protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
+ {
+ base.OnActivityResult(requestCode, resultCode, data);
+
+ ActivityResult?.Invoke(requestCode, resultCode, data);
+ }
+
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
+ }
+ }
+
+ public abstract partial class AvaloniaMainActivity : AvaloniaMainActivity where TApp : Application, new()
+ {
internal AvaloniaView View;
private GlobalLayoutListener _listener;
protected override void OnCreate(Bundle savedInstanceState)
{
- View = new AvaloniaView(this);
- if (ViewContent != null)
- {
- View.Content = ViewContent;
- }
+ InitializeApp();
- if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
- {
- lifetime.View = View;
- }
base.OnCreate(savedInstanceState);
SetContentView(View);
@@ -41,20 +63,6 @@ namespace Avalonia.Android
View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
}
- public object Content
- {
- get
- {
- return ViewContent;
- }
- set
- {
- ViewContent = value;
- if (View != null)
- View.Content = value;
- }
- }
-
protected override void OnResume()
{
base.OnResume();
@@ -66,20 +74,6 @@ namespace Avalonia.Android
}
}
- public event EventHandler BackRequested;
-
- public override void OnBackPressed()
- {
- var eventArgs = new AndroidBackRequestedEventArgs();
-
- BackRequested?.Invoke(this, eventArgs);
-
- if (!eventArgs.Handled)
- {
- base.OnBackPressed();
- }
- }
-
protected override void OnDestroy()
{
View.Content = null;
@@ -89,20 +83,6 @@ namespace Avalonia.Android
base.OnDestroy();
}
- protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
- {
- base.OnActivityResult(requestCode, resultCode, data);
-
- ActivityResult?.Invoke(requestCode, resultCode, data);
- }
-
- public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
- {
- base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
-
- RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
- }
-
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private AvaloniaView _view;
diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
deleted file mode 100644
index 38038ef26c..0000000000
--- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Android.OS;
-using AndroidX.AppCompat.App;
-
-namespace Avalonia.Android
-{
- public abstract class AvaloniaSplashActivity : AppCompatActivity
- {
- protected abstract AppBuilder CreateAppBuilder();
-
- private static AppBuilder s_appBuilder;
-
- protected override void OnCreate(Bundle? savedInstanceState)
- {
- base.OnCreate(savedInstanceState);
-
- if (s_appBuilder == null)
- {
- var builder = CreateAppBuilder();
-
- var lifetime = new SingleViewLifetime();
-
- builder.SetupWithLifetime(lifetime);
-
- s_appBuilder = builder;
- }
- }
- }
-
- public abstract class AvaloniaSplashActivity : AvaloniaSplashActivity where TApp : Application, new()
- {
- protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
-
- protected override AppBuilder CreateAppBuilder()
- {
- var builder = AppBuilder.Configure();
-
- return CustomizeAppBuilder(builder);
- }
- }
-}
diff --git a/src/Android/Avalonia.Android/InputEditable.cs b/src/Android/Avalonia.Android/InputEditable.cs
deleted file mode 100644
index c5b68d2652..0000000000
--- a/src/Android/Avalonia.Android/InputEditable.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using Android.Runtime;
-using Android.Text;
-using Android.Views;
-using Android.Views.InputMethods;
-using Avalonia.Android.Platform.SkiaPlatform;
-using Avalonia.Controls.Presenters;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Input.TextInput;
-using Java.Lang;
-using static System.Net.Mime.MediaTypeNames;
-
-namespace Avalonia.Android
-{
- internal class InputEditable : SpannableStringBuilder, ITextEditable
- {
- private readonly TopLevelImpl _topLevel;
- private readonly IAndroidInputMethod _inputMethod;
- private readonly AvaloniaInputConnection _avaloniaInputConnection;
- private int _currentBatchLevel;
- private string _previousText;
- private int _previousSelectionStart;
- private int _previousSelectionEnd;
-
- public event EventHandler TextChanged;
- public event EventHandler SelectionChanged;
- public event EventHandler CompositionChanged;
-
- public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
- {
- _topLevel = topLevel;
- _inputMethod = inputMethod;
- _avaloniaInputConnection = avaloniaInputConnection;
- }
-
- public InputEditable(ICharSequence text) : base(text)
- {
- }
-
- public InputEditable(string text) : base(text)
- {
- }
-
- public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
- {
- }
-
- public InputEditable(string text, int start, int end) : base(text, start, end)
- {
- }
-
- protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
- {
- }
-
- public int SelectionStart
- {
- get => Selection.GetSelectionStart(this); set
- {
- var end = SelectionEnd < 0 ? 0 : SelectionEnd;
- _avaloniaInputConnection.SetSelection(value, end);
- _inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
- }
- }
- public int SelectionEnd
- {
- get => Selection.GetSelectionEnd(this); set
- {
- var start = SelectionStart < 0 ? 0 : SelectionStart;
- _avaloniaInputConnection.SetSelection(start, value);
- _inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
- }
- }
-
- public string? Text
- {
- get => ToString(); set
- {
- if (Text != value)
- {
- Clear();
- Insert(0, value ?? "");
- }
- }
- }
-
- public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
-
- public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
-
- public void BeginBatchEdit()
- {
- _currentBatchLevel++;
-
- if (_currentBatchLevel == 1)
- {
- _previousText = ToString();
- _previousSelectionStart = SelectionStart;
- _previousSelectionEnd = SelectionEnd;
- }
- }
-
- public void EndBatchEdit()
- {
- if (_currentBatchLevel == 1)
- {
- if(_previousText != Text)
- {
- TextChanged?.Invoke(this, EventArgs.Empty);
- }
-
- if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
- {
- SelectionChanged?.Invoke(this, EventArgs.Empty);
- }
- }
-
- _currentBatchLevel--;
- }
-
- public void RaiseCompositionChanged()
- {
- CompositionChanged?.Invoke(this, EventArgs.Empty);
- }
- }
-}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
index 56a4eb22d4..365c9cdb42 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
@@ -13,5 +13,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface, _topLevel.RenderScaling);
+
+ public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index a29f06a106..a6e8d10777 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
+using System.Threading;
using Android.App;
using Android.Content;
using Android.Graphics;
@@ -11,6 +12,7 @@ using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
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;
@@ -28,6 +30,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
+using static System.Net.Mime.MediaTypeNames;
using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform
@@ -429,79 +432,187 @@ namespace Avalonia.Android.Platform.SkiaPlatform
activity.Window.Attributes = attr;
}
}
+
+ internal void TextInput(string text)
+ {
+ if(Input != null)
+ {
+ var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, InputRoot, text);
+
+ Input(args);
+ }
+ }
+ }
+
+ internal class EditableWrapper : SpannableStringBuilder
+ {
+ private readonly AvaloniaInputConnection _inputConnection;
+
+ public event EventHandler SelectionChanged;
+
+ public EditableWrapper(AvaloniaInputConnection inputConnection)
+ {
+ _inputConnection = inputConnection;
+ }
+
+ public TextSelection CurrentSelection => new TextSelection(Selection.GetSelectionStart(this), Selection.GetSelectionEnd(this));
+ public TextSelection CurrentComposition => new TextSelection(BaseInputConnection.GetComposingSpanStart(this), BaseInputConnection.GetComposingSpanEnd(this));
+
+ public bool IgnoreChange { get; set; }
+
+ public override IEditable Replace(int start, int end, ICharSequence tb)
+ {
+ if (!IgnoreChange && start != end)
+ {
+ SelectSurroundingTextForDeletion(start, end);
+ }
+
+ return base.Replace(start, end, tb);
+ }
+
+ public override IEditable Replace(int start, int end, ICharSequence tb, int tbstart, int tbend)
+ {
+ if (!IgnoreChange && start != end)
+ {
+ SelectSurroundingTextForDeletion(start, end);
+ }
+
+ return base.Replace(start, end, tb, tbstart, tbend);
+ }
+
+ private void SelectSurroundingTextForDeletion(int start, int end)
+ {
+ _inputConnection.InputMethod.Client.Selection = new TextSelection(start, end);
+ }
}
internal class AvaloniaInputConnection : BaseInputConnection
{
- private readonly TopLevelImpl _topLevel;
+ private readonly TopLevelImpl _toplevel;
private readonly IAndroidInputMethod _inputMethod;
- private readonly InputEditable _editable;
+ private readonly EditableWrapper _editable;
+ private bool _commitInProgress;
+ private int _batchLevel = 0;
- public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
+ public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
- _topLevel = topLevel;
+ _toplevel = toplevel;
_inputMethod = inputMethod;
- _editable = new InputEditable(_topLevel, _inputMethod, this);
+ _editable = new EditableWrapper(this);
}
+ public int ExtractedTextToken { get; private set; }
+
public override IEditable Editable => _editable;
- internal InputEditable InputEditable => _editable;
+ public EditableWrapper EditableWrapper => _editable;
- public override bool SetComposingRegion(int start, int end)
- {
- var ret = base.SetComposingRegion(start, end);
+ public IAndroidInputMethod InputMethod => _inputMethod;
+
+ public TopLevelImpl Toplevel => _toplevel;
- InputEditable.RaiseCompositionChanged();
+ public bool IsInBatchEdit => _batchLevel > 0;
- return ret;
+ public override bool SetComposingRegion(int start, int end)
+ {
+ return base.SetComposingRegion(start, end);
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
- var composingText = text.ToString();
+ BeginBatchEdit();
+ _editable.IgnoreChange = true;
- if (string.IsNullOrEmpty(composingText))
+ try
{
- return CommitText(text, newCursorPosition);
+ if (_editable.CurrentComposition.Start > -1)
+ {
+ // Select the composing region.
+ InputMethod.Client.Selection = new TextSelection(_editable.CurrentComposition.Start, _editable.CurrentComposition.End);
+ }
+ var compositionText = text.SubSequence(0, text.Length());
+
+ if (_inputMethod.IsActive && !_commitInProgress)
+ {
+ if (string.IsNullOrEmpty(compositionText))
+ _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+
+ else
+ _toplevel.TextInput(compositionText);
+ }
+ base.SetComposingText(text, newCursorPosition);
}
- else
+ finally
{
- var ret = base.SetComposingText(text, newCursorPosition);
-
- InputEditable.RaiseCompositionChanged();
+ _editable.IgnoreChange = false;
- return ret;
+ EndBatchEdit();
}
+
+ return true;
}
public override bool BeginBatchEdit()
{
- _editable.BeginBatchEdit();
-
+ _batchLevel = Interlocked.Increment(ref _batchLevel);
return base.BeginBatchEdit();
}
public override bool EndBatchEdit()
{
- var ret = base.EndBatchEdit();
- _editable.EndBatchEdit();
+ _batchLevel = Interlocked.Decrement(ref _batchLevel);
- return ret;
+ _inputMethod.OnBatchEditedEnded();
+ return base.EndBatchEdit();
}
- public override bool FinishComposingText()
+ public override bool CommitText(ICharSequence text, int newCursorPosition)
{
- var ret = base.FinishComposingText();
- InputEditable.RaiseCompositionChanged();
- return ret;
+ BeginBatchEdit();
+ _commitInProgress = true;
+
+ var composingRegion = _editable.CurrentComposition;
+
+ var ret = base.CommitText(text, newCursorPosition);
+
+ if(composingRegion.Start != -1)
+ {
+ InputMethod.Client.Selection = composingRegion;
+ }
+
+ var committedText = text.SubSequence(0, text.Length());
+
+ if (_inputMethod.IsActive)
+ if (string.IsNullOrEmpty(committedText))
+ _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+ else
+ _toplevel.TextInput(committedText);
+
+ _commitInProgress = false;
+ EndBatchEdit();
+
+ return true;
}
- public override bool CommitText(ICharSequence text, int newCursorPosition)
+ public override bool DeleteSurroundingText(int beforeLength, int afterLength)
{
- var ret = base.CommitText(text, newCursorPosition);
- InputEditable.RaiseCompositionChanged();
- return ret;
+ if (InputMethod.IsActive)
+ {
+ EditableWrapper.IgnoreChange = true;
+ }
+
+ if (InputMethod.IsActive)
+ {
+ var selection = _editable.CurrentSelection;
+
+ InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength);
+
+ InputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+
+ EditableWrapper.IgnoreChange = true;
+ }
+
+ return true;
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
@@ -517,5 +628,48 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.PerformEditorAction(actionCode);
}
+
+ public override ExtractedText GetExtractedText(ExtractedTextRequest request, [GeneratedEnum] GetTextFlags flags)
+ {
+ if (request == null)
+ return null;
+
+ ExtractedTextToken = request.Token;
+
+ var editable = Editable;
+
+ if (editable == null)
+ {
+ return null;
+ }
+
+ if (!_inputMethod.IsActive)
+ {
+ return null;
+ }
+
+ var selection = _editable.CurrentSelection;
+
+ ExtractedText extract = new ExtractedText
+ {
+ Flags = 0,
+ PartialStartOffset = -1,
+ PartialEndOffset = -1,
+ SelectionStart = selection.Start,
+ SelectionEnd = selection.End,
+ StartOffset = 0
+ };
+
+ if ((request.Flags & GetTextFlags.WithStyles) != 0)
+ {
+ extract.Text = new SpannableString(editable);
+ }
+ else
+ {
+ extract.Text = editable;
+ }
+
+ return extract;
+ }
}
}
diff --git a/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
new file mode 100644
index 0000000000..6aaafa1b96
--- /dev/null
+++ b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Animation.Animators;
+using Avalonia.Media;
+
+namespace Avalonia.Animation;
+
+partial class Animation
+{
+ ///
+ /// Sets the value of the Animator attached property for a setter.
+ ///
+ /// The animation setter.
+ /// The property animator value.
+ [Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator", true)]
+ public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
+ {
+ s_animators[setter] = (value.WrapperType, value.CreateWrapper);
+ }
+
+ ///
+ /// Sets the value of the Animator attached property for a setter.
+ ///
+ /// The animation setter.
+ /// The property animator value.
+ public static void SetAnimator(IAnimationSetter setter, ICustomAnimator value)
+ {
+ s_animators[setter] = (value.WrapperType, value.CreateWrapper);
+ }
+
+ private readonly static List<(Func Condition, Type Animator, Func Factory)>
+ Animators = new()
+ {
+ (prop =>(typeof(double).IsAssignableFrom(prop.PropertyType) && typeof(Transform).IsAssignableFrom(prop.OwnerType)),
+ typeof(TransformAnimator), () => new TransformAnimator()),
+ (prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator()),
+ (prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator()),
+ (prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator()),
+ (prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator()),
+ (prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator()),
+ (prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator()),
+ (prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator()),
+ (prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator()),
+ (prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator()),
+ (prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator()),
+ (prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator()),
+ };
+
+ static Animation()
+ {
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ RegisterAnimator();
+ }
+
+ ///
+ /// Registers a that can handle
+ /// a value type that matches the specified condition.
+ ///
+ static void RegisterAnimator()
+ where TAnimator : Animator, new()
+ {
+ Animators.Insert(0,
+ (prop => typeof(T).IsAssignableFrom(prop.PropertyType), typeof(TAnimator), () => new TAnimator()));
+ }
+
+ public static void RegisterCustomAnimator() where TAnimator : InterpolatingAnimator, new()
+ {
+ Animators.Insert(0, (prop => typeof(T).IsAssignableFrom(prop.PropertyType),
+ typeof(InterpolatingAnimator.AnimatorWrapper), () => new TAnimator().CreateWrapper()));
+ }
+
+ private static (Type Type, Func Factory)? GetAnimatorType(AvaloniaProperty property)
+ {
+ foreach (var (condition, type, factory) in Animators)
+ {
+ if (condition(property))
+ {
+ return (type, factory);
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs
index 4de89bfe97..f584cad951 100644
--- a/src/Avalonia.Base/Animation/Animation.cs
+++ b/src/Avalonia.Base/Animation/Animation.cs
@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Reactive;
using System.Threading;
using System.Threading.Tasks;
-
-using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Data;
using Avalonia.Metadata;
@@ -16,7 +13,7 @@ namespace Avalonia.Animation
///
/// Tracks the progress of an animation.
///
- public sealed class Animation : AvaloniaObject, IAnimation
+ public sealed partial class Animation : AvaloniaObject, IAnimation
{
///
/// Defines the property.
@@ -195,60 +192,6 @@ namespace Avalonia.Animation
return null;
}
- ///
- /// Sets the value of the Animator attached property for a setter.
- ///
- /// The animation setter.
- /// The property animator value.
- public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
- {
- s_animators[setter] = (value.WrapperType, value.CreateWrapper);
- }
-
- private readonly static List<(Func Condition, Type Animator, Func Factory)> Animators = new()
- {
- ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ),
- ( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ),
- ( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ),
- ( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ),
- ( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ),
- ( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ),
- ( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ),
- ( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ),
- ( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ),
- ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ),
- ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ),
- };
-
- ///
- /// Registers a that can handle
- /// a value type that matches the specified condition.
- ///
- ///
- /// The condition to which the
- /// is to be activated and used.
- ///
- ///
- /// The type of the animator to instantiate.
- ///
- internal static void RegisterAnimator(Func condition)
- where TAnimator : IAnimator, new()
- {
- Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
- }
-
- private static (Type Type, Func Factory)? GetAnimatorType(AvaloniaProperty property)
- {
- foreach (var (condition, type, factory) in Animators)
- {
- if (condition(property))
- {
- return (type, factory);
- }
- }
- return null;
- }
-
private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func>();
diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs
index 682629c801..9d4575b9ad 100644
--- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs
+++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs
@@ -53,10 +53,13 @@ namespace Avalonia.Animation
private void FetchProperties()
{
if (_animation.SpeedRatio < 0d)
- throw new ArgumentOutOfRangeException("SpeedRatio value should not be negative.");
+ throw new InvalidOperationException("SpeedRatio value should not be negative.");
- if (_animation.Duration.TotalSeconds <= 0)
- throw new InvalidOperationException("Duration value cannot be negative or zero.");
+ if (_animation.Duration < TimeSpan.Zero)
+ throw new InvalidOperationException("Duration value cannot be negative.");
+
+ if (_animation.Delay < TimeSpan.Zero)
+ throw new InvalidOperationException("Delay value cannot be negative.");
_easeFunc = _animation.Easing;
@@ -110,8 +113,8 @@ namespace Avalonia.Animation
{
if (_animator.Property is null)
throw new InvalidOperationException("Animator has no property specified.");
- if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
- _targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue);
+ if (_fillMode is FillMode.Forward or FillMode.Both)
+ _targetControl.SetValue(_animator.Property, _lastInterpValue);
}
private void DoComplete()
@@ -123,11 +126,8 @@ namespace Avalonia.Animation
private void DoDelay()
{
- if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both)
- if (_currentIteration == 0)
- PublishNext(_firstKFValue);
- else
- PublishNext(_lastInterpValue);
+ if (_fillMode is not (FillMode.Backward or FillMode.Both)) return;
+ PublishNext(_currentIteration == 0 ? _firstKFValue : _lastInterpValue);
}
private void DoPlayStates()
@@ -154,72 +154,74 @@ namespace Avalonia.Animation
var iterDelay = _iterationDelay.Ticks * _speedRatioConv;
var initDelay = _initialDelay.Ticks * _speedRatioConv;
- if (indexTime > 0 & indexTime <= initDelay)
+ // This conditional checks if the time given is the very start/zero
+ // and when we have an active delay time.
+ if (initDelay > 0 && indexTime <= initDelay)
{
DoDelay();
+ return;
}
- else
+
+ // Calculate timebases.
+ var iterationTime = iterDuration + iterDelay;
+ var opsTime = indexTime - initDelay;
+ var playbackTime = opsTime % iterationTime;
+
+ _currentIteration = (ulong)(opsTime / iterationTime);
+
+ // Stop animation when the current iteration is beyond the iteration count or
+ // when the duration is set to zero while animating and snap to the last iterated value.
+ if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero)
{
- // Calculate timebases.
- var iterationTime = iterDuration + iterDelay;
- var opsTime = indexTime - initDelay;
- var playbackTime = opsTime % iterationTime;
+ var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0);
+ _lastInterpValue = _interpolator(easedTime, _neutralValue);
+ DoComplete();
+ }
- _currentIteration = (ulong)(opsTime / iterationTime);
+ if (playbackTime <= iterDuration)
+ {
+ // Normalize time for interpolation.
+ var normalizedTime = playbackTime / iterDuration;
- // Stop animation when the current iteration is beyond the iteration count
- // and snap the last iteration value to exact values.
- if ((_currentIteration + 1) > _iterationCount)
- {
- var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0);
- _lastInterpValue = _interpolator(easedTime, _neutralValue);
- DoComplete();
- }
+ // Check if normalized time needs to be reversed according to PlaybackDirection
- if (playbackTime <= iterDuration)
- {
- // Normalize time for interpolation.
- var normalizedTime = playbackTime / iterDuration;
-
- // Check if normalized time needs to be reversed according to PlaybackDirection
-
- switch (_playbackDirection)
- {
- case PlaybackDirection.Normal:
- _playbackReversed = false;
- break;
- case PlaybackDirection.Reverse:
- _playbackReversed = true;
- break;
- case PlaybackDirection.Alternate:
- _playbackReversed = (_currentIteration % 2 == 0) ? false : true;
- break;
- case PlaybackDirection.AlternateReverse:
- _playbackReversed = (_currentIteration % 2 == 0) ? true : false;
- break;
- default:
- throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
- }
-
- if (_playbackReversed)
- normalizedTime = 1 - normalizedTime;
-
- // Ease and interpolate
- var easedTime = _easeFunc!.Ease(normalizedTime);
- _lastInterpValue = _interpolator(easedTime, _neutralValue);
-
- PublishNext(_lastInterpValue);
- }
- else if (playbackTime > iterDuration &
- playbackTime <= iterationTime &
- iterDelay > 0)
+ switch (_playbackDirection)
{
- // The last iteration's trailing delay should be skipped.
- if ((_currentIteration + 1) < _iterationCount)
- DoDelay();
- else
- DoComplete();
+ case PlaybackDirection.Normal:
+ _playbackReversed = false;
+ break;
+ case PlaybackDirection.Reverse:
+ _playbackReversed = true;
+ break;
+ case PlaybackDirection.Alternate:
+ _playbackReversed = _currentIteration % 2 != 0;
+ break;
+ case PlaybackDirection.AlternateReverse:
+ _playbackReversed = _currentIteration % 2 == 0;
+ break;
+ default:
+ throw new InvalidOperationException(
+ $"Animation direction value is unknown: {_playbackDirection}");
}
+
+ if (_playbackReversed)
+ normalizedTime = 1 - normalizedTime;
+
+ // Ease and interpolate
+ var easedTime = _easeFunc!.Ease(normalizedTime);
+ _lastInterpValue = _interpolator(easedTime, _neutralValue);
+
+ PublishNext(_lastInterpValue);
+ }
+ else if (playbackTime > iterDuration &&
+ playbackTime <= iterationTime &&
+ iterDelay > 0)
+ {
+ // The last iteration's trailing delay should be skipped.
+ if (_currentIteration + 1 < _iterationCount)
+ DoDelay();
+ else
+ DoComplete();
}
}
diff --git a/src/Avalonia.Base/Animation/CrossFade.cs b/src/Avalonia.Base/Animation/CrossFade.cs
index 640d6456a3..d598db870f 100644
--- a/src/Avalonia.Base/Animation/CrossFade.cs
+++ b/src/Avalonia.Base/Animation/CrossFade.cs
@@ -35,6 +35,18 @@ namespace Avalonia.Animation
{
Children =
{
+ new KeyFrame()
+ {
+ Setters =
+ {
+ new Setter
+ {
+ Property = Visual.OpacityProperty,
+ Value = 1d
+ }
+ },
+ Cue = new Cue(0d)
+ },
new KeyFrame()
{
Setters =
@@ -54,6 +66,18 @@ namespace Avalonia.Animation
{
Children =
{
+ new KeyFrame()
+ {
+ Setters =
+ {
+ new Setter
+ {
+ Property = Visual.OpacityProperty,
+ Value = 0d
+ }
+ },
+ Cue = new Cue(0d)
+ },
new KeyFrame()
{
Setters =
@@ -117,11 +141,13 @@ namespace Avalonia.Animation
if (from != null)
{
+ from.Opacity = 0f;
tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken));
}
if (to != null)
{
+ to.Opacity = 1f;
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken));
}
diff --git a/src/Avalonia.Base/Animation/ICustomAnimator.cs b/src/Avalonia.Base/Animation/ICustomAnimator.cs
index 88c9974ca6..119a6115da 100644
--- a/src/Avalonia.Base/Animation/ICustomAnimator.cs
+++ b/src/Avalonia.Base/Animation/ICustomAnimator.cs
@@ -1,14 +1,15 @@
using System;
using Avalonia.Animation.Animators;
-
namespace Avalonia.Animation;
+[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator", true)]
public abstract class CustomAnimatorBase
{
internal abstract IAnimator CreateWrapper();
internal abstract Type WrapperType { get; }
}
+[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator", true)]
public abstract class CustomAnimatorBase : CustomAnimatorBase
{
public abstract T Interpolate(double progress, T oldValue, T newValue);
@@ -25,6 +26,33 @@ public abstract class CustomAnimatorBase : CustomAnimatorBase
_parent = parent;
}
+ public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
+ }
+}
+
+public interface ICustomAnimator
+{
+ internal IAnimator CreateWrapper();
+ internal Type WrapperType { get; }
+}
+
+public abstract class InterpolatingAnimator : ICustomAnimator
+{
+ public abstract T Interpolate(double progress, T oldValue, T newValue);
+
+ Type ICustomAnimator.WrapperType => typeof(AnimatorWrapper);
+ IAnimator ICustomAnimator.CreateWrapper() => new AnimatorWrapper(this);
+ internal IAnimator CreateWrapper() => new AnimatorWrapper(this);
+
+ internal class AnimatorWrapper : Animator
+ {
+ private readonly InterpolatingAnimator _parent;
+
+ public AnimatorWrapper(InterpolatingAnimator parent)
+ {
+ _parent = parent;
+ }
+
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs b/src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs
new file mode 100644
index 0000000000..44e3129b76
--- /dev/null
+++ b/src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs
@@ -0,0 +1,28 @@
+using System;
+using Avalonia.Animation.Animators;
+
+namespace Avalonia.Animation;
+
+///
+/// The base class for user-defined transition that are doing simple value interpolation
+///
+public abstract class InterpolatingTransitionBase : Transition
+{
+ class Animator : Animator
+ {
+ private readonly InterpolatingTransitionBase _parent;
+
+ public Animator(InterpolatingTransitionBase parent)
+ {
+ _parent = parent;
+ }
+
+ public override T Interpolate(double progress, T oldValue, T newValue) =>
+ _parent.Interpolate(progress, oldValue, newValue);
+ }
+
+ protected abstract T Interpolate(double progress, T from, T to);
+
+ internal override IObservable DoTransition(IObservable progress, T oldValue, T newValue) =>
+ new AnimatorTransitionObservable(new Animator(this), progress, Easing, oldValue, newValue);
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
index b4a1f02f09..fb8e9488a7 100644
--- a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
+++ b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
@@ -89,6 +89,7 @@ public class Rotate3DTransition: PageSlide
{
Easing = SlideInEasing,
Duration = Duration,
+ FillMode = FillMode.Forward,
Children =
{
CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1),
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index ddc2b7effb..16eb09de65 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -43,21 +43,15 @@
-
-
-
-
-
-
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index 0c22213d33..833cd4b034 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -60,6 +60,8 @@ namespace Avalonia
}
///
+ /// The type of the values held by the .
+ /// The type of the value returned by the .
///
///
/// A method which is executed to convert each property value to .
@@ -71,6 +73,15 @@ namespace Avalonia
converter ?? throw new ArgumentNullException(nameof(converter)));
}
+ ///
+ public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property, Func
/// The type of the property change sender.
- /// /// The type of the property..
+ /// The type of the property.
/// The property changed observable.
///
/// The method to call. The parameters are the sender and the event args.
diff --git a/src/Avalonia.Base/CombinedGeometry.cs b/src/Avalonia.Base/CombinedGeometry.cs
index 4b5866519b..a47e756c88 100644
--- a/src/Avalonia.Base/CombinedGeometry.cs
+++ b/src/Avalonia.Base/CombinedGeometry.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Text;
using Avalonia.Platform;
-#nullable enable
-
namespace Avalonia.Media
{
public enum GeometryCombineMode
@@ -147,7 +145,7 @@ namespace Avalonia.Media
return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform);
}
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var g1 = Geometry1;
var g2 = Geometry2;
diff --git a/src/Avalonia.Base/Compatibility/NativeLibrary.cs b/src/Avalonia.Base/Compatibility/NativeLibrary.cs
new file mode 100644
index 0000000000..7627c095bc
--- /dev/null
+++ b/src/Avalonia.Base/Compatibility/NativeLibrary.cs
@@ -0,0 +1,122 @@
+using System;
+using System.ComponentModel;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Avalonia.Compatibility;
+using Avalonia.Platform.Interop;
+
+namespace Avalonia.Compatibility
+{
+ internal class NativeLibraryEx
+ {
+#if NET6_0_OR_GREATER
+ public static IntPtr Load(string dll, Assembly assembly) => NativeLibrary.Load(dll, assembly, null);
+ public static IntPtr Load(string dll) => NativeLibrary.Load(dll);
+ public static bool TryGetExport(IntPtr handle, string name, out IntPtr address) =>
+ NativeLibrary.TryGetExport(handle, name, out address);
+#else
+ public static IntPtr Load(string dll, Assembly assembly) => Load(dll);
+ public static IntPtr Load(string dll)
+ {
+ var handle = DlOpen!(dll);
+ if (handle != IntPtr.Zero)
+ return handle;
+ throw new InvalidOperationException("Unable to load " + dll, DlError!());
+ }
+
+ public static bool TryGetExport(IntPtr handle, string name, out IntPtr address)
+ {
+ try
+ {
+ address = DlSym!(handle, name);
+ return address != default;
+ }
+ catch (Exception)
+ {
+ address = default;
+ return false;
+ }
+ }
+
+ static NativeLibraryEx()
+ {
+ if (OperatingSystemEx.IsWindows())
+ {
+ Win32Imports.Init();
+ }
+ else if (OperatingSystemEx.IsLinux() || OperatingSystemEx.IsMacOS())
+ {
+ var buffer = Marshal.AllocHGlobal(0x1000);
+ uname(buffer);
+ var unixName = Marshal.PtrToStringAnsi(buffer);
+ Marshal.FreeHGlobal(buffer);
+ if (unixName == "Darwin")
+ OsXImports.Init();
+ else
+ LinuxImports.Init();
+ }
+ }
+
+ private static Func? DlOpen;
+ private static Func? DlSym;
+ private static Func? DlError;
+
+ [DllImport("libc")]
+ static extern int uname(IntPtr buf);
+
+ static class Win32Imports
+ {
+ [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
+ private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
+
+ [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern IntPtr LoadLibrary(string lpszLib);
+
+ public static void Init()
+ {
+ DlOpen = LoadLibrary;
+ DlSym = GetProcAddress;
+ DlError = () => new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ }
+
+ static class LinuxImports
+ {
+ [DllImport("libdl.so.2")]
+ private static extern IntPtr dlopen(string path, int flags);
+
+ [DllImport("libdl.so.2")]
+ private static extern IntPtr dlsym(IntPtr handle, string symbol);
+
+ [DllImport("libdl.so.2")]
+ private static extern IntPtr dlerror();
+
+ public static void Init()
+ {
+ DlOpen = s => dlopen(s, 1);
+ DlSym = dlsym;
+ DlError = () => new InvalidOperationException(Marshal.PtrToStringAnsi(dlerror()));
+ }
+ }
+
+ static class OsXImports
+ {
+ [DllImport("/usr/lib/libSystem.dylib")]
+ private static extern IntPtr dlopen(string path, int flags);
+
+ [DllImport("/usr/lib/libSystem.dylib")]
+ private static extern IntPtr dlsym(IntPtr handle, string symbol);
+
+ [DllImport("/usr/lib/libSystem.dylib")]
+ private static extern IntPtr dlerror();
+
+ public static void Init()
+ {
+ DlOpen = s => dlopen(s, 1);
+ DlSym = dlsym;
+ DlError = () => new InvalidOperationException(Marshal.PtrToStringAnsi(dlerror()));
+ }
+ }
+#endif
+ }
+}
diff --git a/src/Avalonia.Base/CornerRadius.cs b/src/Avalonia.Base/CornerRadius.cs
index 82791999d7..44b45c2a73 100644
--- a/src/Avalonia.Base/CornerRadius.cs
+++ b/src/Avalonia.Base/CornerRadius.cs
@@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct CornerRadius : IEquatable
{
- static CornerRadius()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
public CornerRadius(double uniformRadius)
{
TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;
diff --git a/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs b/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
index 24149c17e0..6027676501 100644
--- a/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
+++ b/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
@@ -60,7 +60,8 @@ namespace Avalonia.Data.Core
var target = Expression.Parameter(typeof(object), "target");
return Expression.Lambda>(
Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType!), info.GetMethod),
- typeof(object)))
+ typeof(object)),
+ target)
.Compile();
}
diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs
index 2e0268a644..38692d8b77 100644
--- a/src/Avalonia.Base/Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs
@@ -159,8 +159,6 @@ namespace Avalonia.Input
_restoreFocusElement?.Focus();
_restoreFocusElement = null;
-
- e.Handled = true;
}
}
else if (_altIsDown)
@@ -200,7 +198,6 @@ namespace Avalonia.Input
if (match is not null)
{
match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent));
- e.Handled = true;
}
}
}
@@ -225,7 +222,6 @@ namespace Avalonia.Input
else if (_showingAccessKeys && MainMenu != null)
{
MainMenu.Open();
- e.Handled = true;
}
break;
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
new file mode 100644
index 0000000000..2cb97b4707
--- /dev/null
+++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
@@ -0,0 +1,37 @@
+namespace Avalonia.Input.GestureRecognizers
+{
+ public abstract class GestureRecognizer : StyledElement
+ {
+ protected internal IInputElement? Target { get; internal set; }
+
+ protected abstract void PointerPressed(PointerPressedEventArgs e);
+ protected abstract void PointerReleased(PointerReleasedEventArgs e);
+ protected abstract void PointerMoved(PointerEventArgs e);
+ protected abstract void PointerCaptureLost(IPointer pointer);
+
+ internal void PointerPressedInternal(PointerPressedEventArgs e)
+ {
+ PointerPressed(e);
+ }
+
+ internal void PointerReleasedInternal(PointerReleasedEventArgs e)
+ {
+ PointerReleased(e);
+ }
+
+ internal void PointerMovedInternal(PointerEventArgs e)
+ {
+ PointerMoved(e);
+ }
+
+ internal void PointerCaptureLostInternal(IPointer pointer)
+ {
+ PointerCaptureLost(pointer);
+ }
+
+ protected void Capture(IPointer pointer)
+ {
+ (pointer as Pointer)?.CaptureGestureRecognizer(this);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
index 3b9b2d0de6..05dce8214b 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
@@ -6,29 +6,26 @@ using Avalonia.Reactive;
namespace Avalonia.Input.GestureRecognizers
{
- public class GestureRecognizerCollection : IReadOnlyCollection, IGestureRecognizerActionsDispatcher
+ public class GestureRecognizerCollection : IReadOnlyCollection
{
private readonly IInputElement _inputElement;
- private List? _recognizers;
- private Dictionary? _pointerGrabs;
-
+ private List? _recognizers;
public GestureRecognizerCollection(IInputElement inputElement)
{
_inputElement = inputElement;
}
- public void Add(IGestureRecognizer recognizer)
+ public void Add(GestureRecognizer recognizer)
{
if (_recognizers == null)
{
// We initialize the collection when the first recognizer is added
- _recognizers = new List();
- _pointerGrabs = new Dictionary();
+ _recognizers = new List();
}
_recognizers.Add(recognizer);
- recognizer.Initialize(_inputElement, this);
+ recognizer.Target = _inputElement;
// Hacks to make bindings work
@@ -41,25 +38,22 @@ namespace Avalonia.Input.GestureRecognizers
}
}
- static readonly List s_Empty = new List();
+ static readonly List s_Empty = new List();
- public IEnumerator GetEnumerator()
+ public IEnumerator GetEnumerator()
=> _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => _recognizers?.Count ?? 0;
-
internal bool HandlePointerPressed(PointerPressedEventArgs e)
{
if (_recognizers == null)
return false;
foreach (var r in _recognizers)
{
- if (e.Handled)
- break;
- r.PointerPressed(e);
+ r.PointerPressedInternal(e);
}
return e.Handled;
@@ -69,17 +63,15 @@ namespace Avalonia.Input.GestureRecognizers
{
if (_recognizers == null)
return false;
- if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
+ var pointer = e.Pointer as Pointer;
+
+ foreach (var r in _recognizers)
{
- capture.PointerReleased(e);
+ if (pointer?.CapturedGestureRecognizer != null)
+ break;
+
+ r.PointerReleasedInternal(e);
}
- else
- foreach (var r in _recognizers)
- {
- if (e.Handled)
- break;
- r.PointerReleased(e);
- }
return e.Handled;
}
@@ -87,41 +79,16 @@ namespace Avalonia.Input.GestureRecognizers
{
if (_recognizers == null)
return false;
- if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
- {
- capture.PointerMoved(e);
- }
- else
- foreach (var r in _recognizers)
- {
- if (e.Handled)
- break;
- r.PointerMoved(e);
- }
- return e.Handled;
- }
+ var pointer = e.Pointer as Pointer;
- internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e)
- {
- if (_recognizers == null)
- return;
- _pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers)
{
- r.PointerCaptureLost(e.Pointer);
- }
- }
+ if (pointer?.CapturedGestureRecognizer != null)
+ break;
- void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
- {
- pointer.Capture(_inputElement);
- _pointerGrabs![pointer] = recognizer;
- foreach (var r in _recognizers!)
- {
- if (r != recognizer)
- r.PointerCaptureLost(pointer);
+ r.PointerMovedInternal(e);
}
+ return e.Handled;
}
-
}
}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs
deleted file mode 100644
index c1d9ae5304..0000000000
--- a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Avalonia.Input.GestureRecognizers
-{
- public interface IGestureRecognizer
- {
- void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions);
- void PointerPressed(PointerPressedEventArgs e);
- void PointerReleased(PointerReleasedEventArgs e);
- void PointerMoved(PointerEventArgs e);
- void PointerCaptureLost(IPointer pointer);
- }
-
- public interface IGestureRecognizerActionsDispatcher
- {
- void Capture(IPointer pointer, IGestureRecognizer recognizer);
- }
-
- public enum GestureRecognizerResult
- {
- None,
- Capture,
- ReleaseCapture
- }
-}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
index 3b83d0cb87..b02c82a066 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
@@ -2,10 +2,8 @@
namespace Avalonia.Input
{
- public class PinchGestureRecognizer : StyledElement, IGestureRecognizer
+ public class PinchGestureRecognizer : GestureRecognizer
{
- private IInputElement? _target;
- private IGestureRecognizerActionsDispatcher? _actions;
private float _initialDistance;
private IPointer? _firstContact;
private Point _firstPoint;
@@ -13,12 +11,6 @@ namespace Avalonia.Input
private Point _secondPoint;
private Point _origin;
- public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
- {
- _target = target;
- _actions = actions;
- }
-
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
@@ -29,14 +21,14 @@ namespace Avalonia.Input
PointerReleased(e);
}
- public void PointerCaptureLost(IPointer pointer)
+ protected override void PointerCaptureLost(IPointer pointer)
{
RemoveContact(pointer);
}
- public void PointerMoved(PointerEventArgs e)
+ protected override void PointerMoved(PointerEventArgs e)
{
- if (_target != null && _target is Visual visual)
+ if (Target != null && Target is Visual visual)
{
if(_firstContact == e.Pointer)
{
@@ -58,16 +50,16 @@ namespace Avalonia.Input
var scale = distance / _initialDistance;
var pinchEventArgs = new PinchEventArgs(scale, _origin);
- _target?.RaiseEvent(pinchEventArgs);
+ Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled;
}
}
}
- public void PointerPressed(PointerPressedEventArgs e)
+ protected override void PointerPressed(PointerPressedEventArgs e)
{
- if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
+ if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
if (_firstContact == null)
{
@@ -92,13 +84,13 @@ namespace Avalonia.Input
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
- _actions!.Capture(_firstContact, this);
- _actions!.Capture(_secondContact, this);
+ Capture(_firstContact);
+ Capture(_secondContact);
}
}
}
- public void PointerReleased(PointerReleasedEventArgs e)
+ protected override void PointerReleased(PointerReleasedEventArgs e)
{
RemoveContact(e.Pointer);
}
@@ -118,7 +110,7 @@ namespace Avalonia.Input
_secondContact = null;
}
- _target?.RaiseEvent(new PinchEndedEventArgs());
+ Target?.RaiseEvent(new PinchEndedEventArgs());
}
}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
index 6784677520..57faf5bfd8 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
@@ -1,15 +1,11 @@
using System;
-using System.Diagnostics;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
- public class PullGestureRecognizer : StyledElement, IGestureRecognizer
+ public class PullGestureRecognizer : GestureRecognizer
{
internal static int MinPullDetectionSize = 50;
-
- private IInputElement? _target;
- private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
@@ -34,13 +30,7 @@ namespace Avalonia.Input
public PullGestureRecognizer() { }
- public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
- {
- _target = target;
- _actions = actions;
- }
-
- public void PointerCaptureLost(IPointer pointer)
+ protected override void PointerCaptureLost(IPointer pointer)
{
if (_tracking == pointer)
{
@@ -48,12 +38,13 @@ namespace Avalonia.Input
}
}
- public void PointerMoved(PointerEventArgs e)
+ protected override void PointerMoved(PointerEventArgs e)
{
- if (_tracking == e.Pointer && _target is Visual visual)
+ if (_tracking == e.Pointer && Target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
- _actions!.Capture(e.Pointer, this);
+ Capture(e.Pointer);
+ e.PreventGestureRecognition();
Vector delta = default;
switch (PullDirection)
@@ -86,15 +77,15 @@ namespace Avalonia.Input
_pullInProgress = true;
var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection);
- _target?.RaiseEvent(pullEventArgs);
+ Target?.RaiseEvent(pullEventArgs);
e.Handled = pullEventArgs.Handled;
}
}
- public void PointerPressed(PointerPressedEventArgs e)
+ protected override void PointerPressed(PointerPressedEventArgs e)
{
- if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
+ if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
var position = e.GetPosition(visual);
@@ -127,7 +118,7 @@ namespace Avalonia.Input
}
}
- public void PointerReleased(PointerReleasedEventArgs e)
+ protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer && _pullInProgress)
{
@@ -141,7 +132,7 @@ namespace Avalonia.Input
_initialPosition = default;
_pullInProgress = false;
- _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
+ Target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
}
}
}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
index b510d44e63..beecfcc3ce 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
@@ -4,7 +4,7 @@ using Avalonia.Threading;
namespace Avalonia.Input.GestureRecognizers
{
- public class ScrollGestureRecognizer : AvaloniaObject, IGestureRecognizer
+ public class ScrollGestureRecognizer : GestureRecognizer
{
// Pixels per second speed that is considered to be the stop of inertial scroll
internal const double InertialScrollSpeedEnd = 5;
@@ -18,8 +18,6 @@ namespace Avalonia.Input.GestureRecognizers
private bool _scrolling;
private Point _trackedRootPoint;
private IPointer? _tracking;
- private IInputElement? _target;
- private IGestureRecognizerActionsDispatcher? _actions;
private int _gestureId;
private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker;
@@ -91,15 +89,9 @@ namespace Avalonia.Input.GestureRecognizers
{
get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
- }
-
- public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
- {
- _target = target;
- _actions = actions;
}
-
- public void PointerPressed(PointerPressedEventArgs e)
+
+ protected override void PointerPressed(PointerPressedEventArgs e)
{
if (e.Pointer.IsPrimary &&
(e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
@@ -107,15 +99,15 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();
- _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target);
+ _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)Target);
}
}
-
- public void PointerMoved(PointerEventArgs e)
+
+ protected override void PointerMoved(PointerEventArgs e)
{
if (e.Pointer == _tracking)
{
- var rootPoint = e.GetPosition((Visual?)_target);
+ var rootPoint = e.GetPosition((Visual?)Target);
if (!_scrolling)
{
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance)
@@ -131,7 +123,9 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
- _actions!.Capture(e.Pointer, this);
+ Capture(e.Pointer);
+
+ e.PreventGestureRecognition();
}
}
@@ -143,13 +137,13 @@ namespace Avalonia.Input.GestureRecognizers
_lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint;
- _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
+ Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
}
}
- public void PointerCaptureLost(IPointer pointer)
+ protected override void PointerCaptureLost(IPointer pointer)
{
if (pointer == _tracking) EndGesture();
}
@@ -161,7 +155,7 @@ namespace Avalonia.Input.GestureRecognizers
{
_inertia = default;
_scrolling = false;
- _target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
+ Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0;
_lastMoveTimestamp = null;
}
@@ -169,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers
}
- public void PointerReleased(PointerReleasedEventArgs e)
+ protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (e.Pointer == _tracking && _scrolling)
{
@@ -188,7 +182,7 @@ namespace Avalonia.Input.GestureRecognizers
var savedGestureId = _gestureId;
var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero;
- _target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
+ Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
DispatcherTimer.Run(() =>
{
// Another gesture has started, finish the current one
@@ -203,7 +197,7 @@ namespace Avalonia.Input.GestureRecognizers
var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds;
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
- _target!.RaiseEvent(scrollGestureEventArgs);
+ Target!.RaiseEvent(scrollGestureEventArgs);
if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
{
diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs
index 167f61eead..e9d23632b1 100644
--- a/src/Avalonia.Base/Input/Gestures.cs
+++ b/src/Avalonia.Base/Input/Gestures.cs
@@ -182,7 +182,7 @@ namespace Avalonia.Input
s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token;
- var settings = AvaloniaLocator.Current.GetService();
+ var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings;
if (settings != null)
{
@@ -221,7 +221,7 @@ namespace Avalonia.Input
e.Source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
- var settings = AvaloniaLocator.Current.GetService();
+ var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
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));
@@ -260,10 +260,10 @@ namespace Avalonia.Input
var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target))
{
- if (e.Pointer == s_lastPointer)
+ if (e.Pointer == s_lastPointer && ev.Source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
- var settings = AvaloniaLocator.Current.GetService();
+ var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
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));
@@ -273,7 +273,7 @@ namespace Avalonia.Input
return;
}
- if (s_isHolding && ev.Source is Interactive i)
+ if (s_isHolding)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type));
}
diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs
index d0921eea2d..2be5847220 100644
--- a/src/Avalonia.Base/Input/IInputRoot.cs
+++ b/src/Avalonia.Base/Input/IInputRoot.cs
@@ -1,4 +1,5 @@
using Avalonia.Metadata;
+using Avalonia.Platform;
namespace Avalonia.Input
{
@@ -17,10 +18,18 @@ namespace Avalonia.Input
/// Gets focus manager of the root.
///
///
- /// Focus manager can be null only if application wasn't initialized yet.
+ /// 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; }
+
///
/// Gets or sets the input element that the pointer is currently over.
///
diff --git a/src/Avalonia.Base/Input/IKeyboardDevice.cs b/src/Avalonia.Base/Input/IKeyboardDevice.cs
index 172b58068c..dfab1cc83d 100644
--- a/src/Avalonia.Base/Input/IKeyboardDevice.cs
+++ b/src/Avalonia.Base/Input/IKeyboardDevice.cs
@@ -43,7 +43,7 @@ namespace Avalonia.Input
PenBarrelButton = 2048
}
- [NotClientImplementable]
+ [PrivateApi]
public interface IKeyboardDevice : IInputDevice
{
}
diff --git a/src/Avalonia.Base/Input/IMouseDevice.cs b/src/Avalonia.Base/Input/IMouseDevice.cs
index 00c436bf21..4fd1ccad22 100644
--- a/src/Avalonia.Base/Input/IMouseDevice.cs
+++ b/src/Avalonia.Base/Input/IMouseDevice.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Input
///
/// Represents a mouse device.
///
- [NotClientImplementable]
+ [PrivateApi]
public interface IMouseDevice : IPointerDevice
{
}
diff --git a/src/Avalonia.Base/Input/IPenDevice.cs b/src/Avalonia.Base/Input/IPenDevice.cs
index 1cc0fcf76d..a6cb669f51 100644
--- a/src/Avalonia.Base/Input/IPenDevice.cs
+++ b/src/Avalonia.Base/Input/IPenDevice.cs
@@ -1,8 +1,11 @@
-namespace Avalonia.Input
+using Avalonia.Metadata;
+
+namespace Avalonia.Input
{
///
/// Represents a pen/stylus device.
///
+ [PrivateApi]
public interface IPenDevice : IPointerDevice
{
diff --git a/src/Avalonia.Base/Input/IPointer.cs b/src/Avalonia.Base/Input/IPointer.cs
index 52605bb6ae..050adbabaa 100644
--- a/src/Avalonia.Base/Input/IPointer.cs
+++ b/src/Avalonia.Base/Input/IPointer.cs
@@ -1,3 +1,4 @@
+using Avalonia.Input.GestureRecognizers;
using Avalonia.Metadata;
namespace Avalonia.Input
diff --git a/src/Avalonia.Base/Input/IPointerDevice.cs b/src/Avalonia.Base/Input/IPointerDevice.cs
index e0aebda9c5..afcea9e4cb 100644
--- a/src/Avalonia.Base/Input/IPointerDevice.cs
+++ b/src/Avalonia.Base/Input/IPointerDevice.cs
@@ -3,7 +3,7 @@ using Avalonia.Metadata;
namespace Avalonia.Input
{
- [NotClientImplementable]
+ [PrivateApi]
public interface IPointerDevice : IInputDevice
{
///
diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs
index 68131e5bf7..46f543d25b 100644
--- a/src/Avalonia.Base/Input/InputElement.cs
+++ b/src/Avalonia.Base/Input/InputElement.cs
@@ -225,6 +225,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e));
+
+ // Gesture only handlers
+ PointerMovedEvent.AddClassHandler((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true);
+ PointerPressedEvent.AddClassHandler((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true);
+ PointerReleasedEvent.AddClassHandler((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true);
}
public InputElement()
@@ -583,10 +588,6 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnPointerMoved(PointerEventArgs e)
{
- if (_gestureRecognizers?.HandlePointerMoved(e) == true)
- {
- e.Handled = true;
- }
}
///
@@ -595,10 +596,6 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnPointerPressed(PointerPressedEventArgs e)
{
- if (_gestureRecognizers?.HandlePointerPressed(e) == true)
- {
- e.Handled = true;
- }
}
///
@@ -607,10 +604,33 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnPointerReleased(PointerReleasedEventArgs e)
{
- if (_gestureRecognizers?.HandlePointerReleased(e) == true)
- {
- e.Handled = true;
- }
+ }
+
+ private void OnGesturePointerReleased(PointerReleasedEventArgs e)
+ {
+ if (!e.IsGestureRecognitionSkipped)
+ if (_gestureRecognizers?.HandlePointerReleased(e) == true)
+ {
+ e.Handled = true;
+ }
+ }
+
+ private void OnGesturePointerPressed(PointerPressedEventArgs e)
+ {
+ if (!e.IsGestureRecognitionSkipped)
+ if (_gestureRecognizers?.HandlePointerPressed(e) == true)
+ {
+ e.Handled = true;
+ }
+ }
+
+ private void OnGesturePointerMoved(PointerEventArgs e)
+ {
+ if (!e.IsGestureRecognitionSkipped)
+ if (_gestureRecognizers?.HandlePointerMoved(e) == true)
+ {
+ e.Handled = true;
+ }
}
///
@@ -619,7 +639,7 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{
- _gestureRecognizers?.HandlePointerCaptureLost(e);
+
}
///
diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs
index 9fa097c4b1..4de0c2d233 100644
--- a/src/Avalonia.Base/Input/KeyEventArgs.cs
+++ b/src/Avalonia.Base/Input/KeyEventArgs.cs
@@ -5,8 +5,6 @@ namespace Avalonia.Input
{
public class KeyEventArgs : RoutedEventArgs
{
- public IKeyboardDevice? Device { get; init; }
-
public Key Key { get; init; }
public KeyModifiers KeyModifiers { get; init; }
diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs
index a81bc6b2e0..c5e3ef5bf0 100644
--- a/src/Avalonia.Base/Input/KeyboardDevice.cs
+++ b/src/Avalonia.Base/Input/KeyboardDevice.cs
@@ -192,7 +192,6 @@ namespace Avalonia.Input
KeyEventArgs ev = new KeyEventArgs
{
RoutedEvent = routedEvent,
- Device = this,
Key = keyInput.Key,
KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
Source = element,
@@ -241,7 +240,6 @@ namespace Avalonia.Input
{
var ev = new TextInputEventArgs()
{
- Device = this,
Text = text.Text,
Source = element,
RoutedEvent = InputElement.TextInputEvent
diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs
index f3e77433a9..db333dbd8b 100644
--- a/src/Avalonia.Base/Input/MouseDevice.cs
+++ b/src/Avalonia.Base/Input/MouseDevice.cs
@@ -2,9 +2,12 @@ using System;
using System.Collections.Generic;
using Avalonia.Reactive;
using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
+using Avalonia.Input.GestureRecognizers;
#pragma warning disable CS0618
namespace Avalonia.Input
@@ -126,9 +129,10 @@ namespace Avalonia.Input
if (source != null)
{
_pointer.Capture(source);
- if (source != null)
+
+ var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ if (settings is not null)
{
- var settings = AvaloniaLocator.Current.GetRequiredService();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Mouse);
@@ -141,11 +145,12 @@ namespace Avalonia.Input
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
- _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
- var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
- source.RaiseEvent(e);
- return e.Handled;
}
+
+ _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
+ var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
+ source.RaiseEvent(e);
+ return e.Handled;
}
return false;
@@ -158,17 +163,21 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var source = _pointer.Captured ?? hitTest;
+ var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest;
if (source is object)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
- source.RaiseEvent(e);
+ if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
+ gestureRecognizer.PointerMovedInternal(e);
+ else
+ source.RaiseEvent(e);
return e.Handled;
}
+
return false;
}
@@ -178,15 +187,19 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var source = _pointer.Captured ?? hitTest;
+ var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
_lastMouseDownButton);
- source?.RaiseEvent(e);
+ if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
+ gestureRecognizer.PointerReleasedInternal(e);
+ else
+ source?.RaiseEvent(e);
_pointer.Capture(null);
+ _pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default;
return e.Handled;
}
diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs
index 832f32fb03..09bf18d3fd 100644
--- a/src/Avalonia.Base/Input/PenDevice.cs
+++ b/src/Avalonia.Base/Input/PenDevice.cs
@@ -2,9 +2,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using Avalonia.Input.GestureRecognizers;
using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Platform;
+using Avalonia.VisualTree;
+
#pragma warning disable CS0618
namespace Avalonia.Input
@@ -80,19 +84,23 @@ namespace Avalonia.Input
if (source != null)
{
pointer.Capture(source);
- var settings = AvaloniaLocator.Current.GetService();
- var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds ?? 500;
- var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Pen) ?? new Size(4, 4);
-
- if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
+ var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ if (settings is not null)
{
- _clickCount = 0;
+ var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds;
+ var doubleClickSize = settings.GetDoubleTapSize(PointerType.Pen);
+
+ if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
+ {
+ _clickCount = 0;
+ }
+
+ ++_clickCount;
+ _lastClickTime = timestamp;
+ _lastClickRect = new Rect(p, new Size())
+ .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
- ++_clickCount;
- _lastClickTime = timestamp;
- _lastClickRect = new Rect(p, new Size())
- .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
@@ -107,14 +115,17 @@ namespace Avalonia.Input
KeyModifiers inputModifiers, IInputElement? hitTest,
Lazy?>? intermediatePoints)
{
- var source = pointer.Captured ?? hitTest;
+ var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
- source.RaiseEvent(e);
+ if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
+ gestureRecognizer.PointerMovedInternal(e);
+ else
+ source.RaiseEvent(e);
return e.Handled;
}
@@ -125,15 +136,19 @@ namespace Avalonia.Input
IInputElement root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
- var source = pointer.Captured ?? 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,
_lastMouseDownButton);
- source.RaiseEvent(e);
+ if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
+ gestureRecognizer.PointerReleasedInternal(e);
+ else
+ source.RaiseEvent(e);
pointer.Capture(null);
+ pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default;
return e.Handled;
}
diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
index b87ca5eded..7ce79e2649 100644
--- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
+++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
@@ -1,16 +1,20 @@
using System.Collections.Generic;
-
-#nullable enable
+using Avalonia.Metadata;
namespace Avalonia.Input.Platform
{
- public class PlatformHotkeyConfiguration
+ ///
+ /// The PlatformHotkeyConfiguration class represents a configuration for platform-specific hotkeys in an Avalonia application.
+ ///
+ public sealed class PlatformHotkeyConfiguration
{
+ [PrivateApi]
public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{
}
+ [PrivateApi]
public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
KeyModifiers selectionModifiers = KeyModifiers.Shift,
KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
@@ -85,6 +89,22 @@ namespace Avalonia.Input.Platform
{
new KeyGesture(Key.Left, KeyModifiers.Alt)
};
+ PageLeft = new List
+ {
+ new KeyGesture(Key.PageUp, KeyModifiers.Shift)
+ };
+ PageRight = new List
+ {
+ new KeyGesture(Key.PageDown, KeyModifiers.Shift)
+ };
+ PageUp = new List
+ {
+ new KeyGesture(Key.PageUp)
+ };
+ PageDown = new List
+ {
+ new KeyGesture(Key.PageDown)
+ };
}
public KeyModifiers CommandModifiers { get; set; }
@@ -106,5 +126,9 @@ namespace Avalonia.Input.Platform
public List MoveCursorToTheEndOfDocumentWithSelection { get; set; }
public List OpenContextMenu { get; set; }
public List Back { get; set; }
+ public List PageUp { get; set; }
+ public List PageDown { get; set; }
+ public List PageRight { get; set; }
+ public List PageLeft { get; set; }
}
}
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index 4713364f00..5744ddf963 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Avalonia.Input.GestureRecognizers;
using Avalonia.VisualTree;
namespace Avalonia.Input
@@ -52,6 +53,9 @@ namespace Avalonia.Input
if (Captured is Visual v3)
v3.DetachedFromVisualTree += OnCaptureDetached;
+
+ if (Captured != null)
+ CaptureGestureRecognizer(null);
}
static IInputElement? GetNextCapture(Visual parent)
@@ -69,6 +73,30 @@ namespace Avalonia.Input
public PointerType Type { get; }
public bool IsPrimary { get; }
- public void Dispose() => Capture(null);
+
+ ///
+ /// Gets the gesture recognizer that is currently capturing by the pointer, if any.
+ ///
+ internal GestureRecognizer? CapturedGestureRecognizer { get; private set; }
+
+ public void Dispose()
+ {
+ Capture(null);
+ }
+
+ ///
+ /// Captures pointer input to the specified gesture recognizer.
+ ///
+ /// The gesture recognizer.
+ internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
+ {
+ if (CapturedGestureRecognizer != gestureRecognizer)
+ CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
+
+ if (gestureRecognizer != null)
+ Capture(null);
+
+ CapturedGestureRecognizer = gestureRecognizer;
+ }
}
}
diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs
index 9c24e5c314..cd742cc4e4 100644
--- a/src/Avalonia.Base/Input/PointerEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerEventArgs.cs
@@ -58,6 +58,8 @@ namespace Avalonia.Input
///
public ulong Timestamp { get; }
+ internal bool IsGestureRecognitionSkipped { get; private set; }
+
///
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
///
@@ -121,6 +123,14 @@ namespace Avalonia.Input
return points;
}
+ ///
+ /// Prevents this event from being handled by other gesture recognizers in the route
+ ///
+ public void PreventGestureRecognition()
+ {
+ IsGestureRecognitionSkipped = true;
+ }
+
///
/// Returns the current pointer point properties
///
diff --git a/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs b/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs
index 3bcc9fadd3..5473b5a9e5 100644
--- a/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs
+++ b/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs
@@ -2,7 +2,7 @@
namespace Avalonia.Input.Raw
{
- [NotClientImplementable]
+ [PrivateApi]
public interface IDragDropDevice : IInputDevice
{
}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextEditable.cs b/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
deleted file mode 100644
index f9f5ec8d40..0000000000
--- a/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Metadata;
-
-namespace Avalonia.Input.TextInput
-{
- [NotClientImplementable]
- public interface ITextEditable
- {
- event EventHandler TextChanged;
- event EventHandler SelectionChanged;
- event EventHandler CompositionChanged;
- int SelectionStart { get; set; }
- int SelectionEnd { get; set; }
- int CompositionStart { get; }
- int CompositionEnd { get; }
-
- string? Text { get; set; }
- }
-}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
deleted file mode 100644
index 2cdcd33626..0000000000
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using Avalonia.Media.TextFormatting;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Input.TextInput
-{
- public interface ITextInputMethodClient
- {
- ///
- /// The cursor rectangle relative to the TextViewVisual
- ///
- Rect CursorRectangle { get; }
- ///
- /// Should be fired when cursor rectangle is changed inside the TextViewVisual
- ///
- event EventHandler? CursorRectangleChanged;
- ///
- /// The visual that's showing the text
- ///
- Visual TextViewVisual { get; }
- ///
- /// Should be fired when text-hosting visual is changed
- ///
- event EventHandler? TextViewVisualChanged;
- ///
- /// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position
- ///
- bool SupportsPreedit { get; }
- ///
- /// Sets the non-committed input string
- ///
- void SetPreeditText(string? text);
-
- ///
- /// Sets the current composing region. This doesn't remove the composing text from the commited text.
- ///
- void SetComposingRegion(TextRange? region);
-
- ///
- /// Indicates if text input client is capable of providing the text around the cursor
- ///
- bool SupportsSurroundingText { get; }
- ///
- /// Returns the text around the cursor, usually the current paragraph, the cursor position inside that text and selection start position
- ///
- TextInputMethodSurroundingText SurroundingText { get; }
- ///
- /// Should be fired when surrounding text changed
- ///
- event EventHandler? SurroundingTextChanged;
-
- ///
- /// Gets or sets a platform editable. Text and selection changes made in the editable are forwarded to the IM client.
- ///
- ITextEditable? TextEditable { get; set; }
-
- void SelectInSurroundingText(int start, int end);
- }
-
- public record struct TextInputMethodSurroundingText
- {
- public string Text { get; set; }
- public int CursorOffset { get; set; }
- public int AnchorOffset { get; set; }
- }
-}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
index be7ad81f81..2969b2e60d 100644
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
+++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Input.TextInput
[Unstable]
public interface ITextInputMethodImpl
{
- void SetClient(ITextInputMethodClient? client);
+ void SetClient(TextInputMethodClient? client);
void SetCursorRect(Rect rect);
void SetOptions(TextInputOptions options);
void Reset();
diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
index 1c61334888..387179b970 100644
--- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
+++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Input.TextInput
{
private ITextInputMethodImpl? _im;
private IInputElement? _focusedElement;
- private ITextInputMethodClient? _client;
+ private TextInputMethodClient? _client;
private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
public TextInputMethodManager()
@@ -16,17 +16,24 @@ namespace Avalonia.Input.TextInput
InputMethod.IsInputMethodEnabledProperty.Changed.Subscribe(OnIsInputMethodEnabledChanged);
}
- private ITextInputMethodClient? Client
+ private TextInputMethodClient? Client
{
get => _client;
set
{
if(_client == value)
+ {
return;
+ }
+
if (_client != null)
{
_client.CursorRectangleChanged -= OnCursorRectangleChanged;
_client.TextViewVisualChanged -= OnTextViewVisualChanged;
+
+ _client = null;
+
+ _im?.Reset();
}
_client = value;
@@ -35,8 +42,6 @@ namespace Avalonia.Input.TextInput
{
_client.CursorRectangleChanged += OnCursorRectangleChanged;
_client.TextViewVisualChanged += OnTextViewVisualChanged;
-
- _im?.Reset();
if (_focusedElement is StyledElement target)
{
@@ -50,6 +55,7 @@ namespace Avalonia.Input.TextInput
_transformTracker.SetVisual(_client?.TextViewVisual);
_im?.SetClient(_client);
+
UpdateCursorRect();
}
else
diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
new file mode 100644
index 0000000000..7cf3752057
--- /dev/null
+++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace Avalonia.Input.TextInput
+{
+ public abstract class TextInputMethodClient
+ {
+ ///
+ /// Fires when the text view visual has changed
+ ///
+ public event EventHandler? TextViewVisualChanged;
+
+ ///
+ /// Fires when the cursor rectangle has changed
+ ///
+ public event EventHandler? CursorRectangleChanged;
+
+ ///
+ /// Fires when the surrounding text has changed
+ ///
+ public event EventHandler? SurroundingTextChanged;
+
+ ///
+ /// Fires when the selection has changed
+ ///
+ public event EventHandler? SelectionChanged;
+
+ ///
+ /// The visual that's showing the text
+ ///
+ public abstract Visual TextViewVisual { get; }
+
+ ///
+ /// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position
+ ///
+ public abstract bool SupportsPreedit { get; }
+
+ ///
+ /// Indicates if text input client is capable of providing the text around the cursor
+ ///
+ public abstract bool SupportsSurroundingText { get; }
+
+ ///
+ /// Returns the text around the cursor, usually the current paragraph
+ ///
+ public abstract string SurroundingText { get; }
+
+ ///
+ /// Gets the cursor rectangle relative to the TextViewVisual
+ ///
+ public abstract Rect CursorRectangle { get; }
+
+ ///
+ /// Gets or sets the curent selection range within current surrounding text.
+ ///
+ public abstract TextSelection Selection { get; set; }
+
+ ///
+ /// Sets the non-committed input string
+ ///
+ public virtual void SetPreeditText(string? preeditText) { }
+
+ protected virtual void RaiseTextViewVisualChanged()
+ {
+ TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void RaiseCursorRectangleChanged()
+ {
+ CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void RaiseSurroundingTextChanged()
+ {
+ SurroundingTextChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void RaiseSelectionChanged()
+ {
+ SelectionChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public record struct TextSelection(int Start, int End);
+}
diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
index bec43487d2..d90d4257fa 100644
--- a/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
+++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
@@ -7,6 +7,6 @@ namespace Avalonia.Input.TextInput
///
/// Set this property to a valid text input client to enable input method interaction
///
- public ITextInputMethodClient? Client { get; set; }
+ public TextInputMethodClient? Client { get; set; }
}
}
diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs
index cda0103749..305b4d81f8 100644
--- a/src/Avalonia.Base/Input/TextInputEventArgs.cs
+++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs
@@ -4,8 +4,6 @@ namespace Avalonia.Input
{
public class TextInputEventArgs : RoutedEventArgs
{
- public IKeyboardDevice? Device { get; set; }
-
public string? Text { get; set; }
}
}
diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs
index 78b570da14..04c444f441 100644
--- a/src/Avalonia.Base/Input/TouchDevice.cs
+++ b/src/Avalonia.Base/Input/TouchDevice.cs
@@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Platform;
+using Avalonia.VisualTree;
+
#pragma warning disable CS0618
namespace Avalonia.Input
@@ -48,7 +51,8 @@ namespace Avalonia.Input
pointer.Capture(hit);
}
- var target = pointer.Captured ?? args.Root;
+ var target = pointer.Captured ?? args.InputHitTestResult ?? args.Root;
+ var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers();
@@ -62,19 +66,23 @@ namespace Avalonia.Input
}
else
{
- var settings = AvaloniaLocator.Current.GetRequiredService();
- var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
- var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
-
- if (!_lastClickRect.Contains(args.Position)
- || ev.Timestamp - _lastClickTime > doubleClickTime)
+ var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ if (settings is not null)
{
- _clickCount = 0;
+ var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
+ var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
+
+ if (!_lastClickRect.Contains(args.Position)
+ || ev.Timestamp - _lastClickTime > doubleClickTime)
+ {
+ _clickCount = 0;
+ }
+
+ ++_clickCount;
+ _lastClickTime = ev.Timestamp;
+ _lastClickRect = new Rect(args.Position, new Size())
+ .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
- ++_clickCount;
- _lastClickTime = ev.Timestamp;
- _lastClickRect = new Rect(args.Position, new Size())
- .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
@@ -88,10 +96,19 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId);
using (pointer)
{
- target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
- (Visual)args.Root, args.Position, ev.Timestamp,
- new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
- keyModifier, MouseButton.Left));
+ target = gestureTarget ?? target;
+ var e = new PointerReleasedEventArgs(target, pointer,
+ (Visual)args.Root, args.Position, ev.Timestamp,
+ new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
+ keyModifier, MouseButton.Left);
+ if (gestureTarget != null)
+ {
+ pointer?.CapturedGestureRecognizer?.PointerReleasedInternal(e);
+ }
+ else
+ {
+ target.RaiseEvent(e);
+ }
}
}
@@ -99,15 +116,28 @@ namespace Avalonia.Input
{
_pointers.Remove(args.RawPointerId);
using (pointer)
- pointer.Capture(null);
+ {
+ pointer?.Capture(null);
+ pointer?.CaptureGestureRecognizer(null);
+ }
}
if (args.Type == RawPointerEventType.TouchUpdate)
{
- target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root,
+ target = gestureTarget ?? target;
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, (Visual)args.Root,
args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
- keyModifier, args.IntermediatePoints));
+ keyModifier, args.IntermediatePoints);
+
+ if (gestureTarget != null)
+ {
+ pointer?.CapturedGestureRecognizer?.PointerMovedInternal(e);
+ }
+ else
+ {
+ target.RaiseEvent(e);
+ }
}
}
diff --git a/src/Avalonia.Base/Interactivity/EventRoute.cs b/src/Avalonia.Base/Interactivity/EventRoute.cs
index 37539a7153..d0d82b4884 100644
--- a/src/Avalonia.Base/Interactivity/EventRoute.cs
+++ b/src/Avalonia.Base/Interactivity/EventRoute.cs
@@ -120,11 +120,6 @@ namespace Avalonia.Interactivity
return;
}
- if (e.Source is null)
- {
- throw new ArgumentException("Event source may not be null", nameof(e));
- }
-
Interactive? lastTarget = null;
var start = 0;
var end = _route.Count;
diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs
index 063c8e99d2..5035d3a48d 100644
--- a/src/Avalonia.Base/Layout/ILayoutManager.cs
+++ b/src/Avalonia.Base/Layout/ILayoutManager.cs
@@ -6,8 +6,8 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- [NotClientImplementable]
- internal interface ILayoutManager : IDisposable
+ [PrivateApi]
+ public interface ILayoutManager : IDisposable
{
///
/// Raised when the layout manager completes a layout pass.
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index 6d698ae5ce..c64c2714c3 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -2,9 +2,9 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using Avalonia.Logging;
using Avalonia.Media;
+using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
@@ -16,7 +16,8 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- internal class LayoutManager : ILayoutManager, IDisposable
+ [PrivateApi]
+ public class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 10;
private readonly Layoutable _owner;
diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs
index 32b2f7a2fb..a0e1873d30 100644
--- a/src/Avalonia.Base/Media/BoxShadow.cs
+++ b/src/Avalonia.Base/Media/BoxShadow.cs
@@ -16,12 +16,6 @@ namespace Avalonia.Media
public Color Color { get; set; }
public bool IsInset { get; set; }
- static BoxShadow()
- {
- Animation.Animation.RegisterAnimator(prop =>
- typeof(BoxShadow).IsAssignableFrom(prop.PropertyType));
- }
-
public bool Equals(in BoxShadow other)
{
return OffsetX.Equals(other.OffsetX) && OffsetY.Equals(other.OffsetY) && Blur.Equals(other.Blur) && Spread.Equals(other.Spread) && Color.Equals(other.Color);
@@ -79,40 +73,44 @@ namespace Avalonia.Media
public override string ToString()
{
var sb = StringBuilderCache.Acquire();
+ ToString(sb);
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+ internal void ToString(StringBuilder sb)
+ {
if (this == default)
{
- return "none";
+ sb.Append("none");
+ return;
}
if (IsInset)
{
- sb.Append("inset");
+ sb.Append("inset ");
}
if (OffsetX != 0.0)
{
- sb.AppendFormat(" {0}", OffsetX.ToString(CultureInfo.InvariantCulture));
+ sb.AppendFormat("{0} ", OffsetX.ToString(CultureInfo.InvariantCulture));
}
if (OffsetY != 0.0)
{
- sb.AppendFormat(" {0}", OffsetY.ToString(CultureInfo.InvariantCulture));
+ sb.AppendFormat("{0} ", OffsetY.ToString(CultureInfo.InvariantCulture));
}
-
+
if (Blur != 0.0)
{
- sb.AppendFormat(" {0}", Blur.ToString(CultureInfo.InvariantCulture));
+ sb.AppendFormat("{0} ", Blur.ToString(CultureInfo.InvariantCulture));
}
if (Spread != 0.0)
{
- sb.AppendFormat(" {0}", Spread.ToString(CultureInfo.InvariantCulture));
+ sb.AppendFormat("{0} ", Spread.ToString(CultureInfo.InvariantCulture));
}
- sb.AppendFormat(" {0}", Color.ToString());
-
- return StringBuilderCache.GetStringAndRelease(sb);
+ sb.AppendFormat("{0}", Color.ToString());
}
public static unsafe BoxShadow Parse(string s)
diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs
index ca16452a96..3ddd8e0a95 100644
--- a/src/Avalonia.Base/Media/BoxShadows.cs
+++ b/src/Avalonia.Base/Media/BoxShadows.cs
@@ -10,12 +10,6 @@ namespace Avalonia.Media
private readonly BoxShadow _first;
private readonly BoxShadow[]? _list;
public int Count { get; }
-
- static BoxShadows()
- {
- Animation.Animation.RegisterAnimator(prop =>
- typeof(BoxShadows).IsAssignableFrom(prop.PropertyType));
- }
public BoxShadows(BoxShadow shadow)
{
@@ -45,20 +39,20 @@ namespace Avalonia.Media
public override string ToString()
{
- var sb = StringBuilderCache.Acquire();
-
if (Count == 0)
{
return "none";
}
+ var sb = StringBuilderCache.Acquire();
foreach (var boxShadow in this)
{
- sb.AppendFormat("{0} ", boxShadow.ToString());
+ boxShadow.ToString(sb);
+ sb.Append(',');
+ sb.Append(' ');
}
-
+ sb.Remove(sb.Length - 2, 2);
return StringBuilderCache.GetStringAndRelease(sb);
-
}
[EditorBrowsable(EditorBrowsableState.Never)]
diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs
index 986e3221e6..21be08b4af 100644
--- a/src/Avalonia.Base/Media/Brush.cs
+++ b/src/Avalonia.Base/Media/Brush.cs
@@ -34,11 +34,6 @@ namespace Avalonia.Media
///
public static readonly StyledProperty TransformOriginProperty =
AvaloniaProperty.Register(nameof(TransformOrigin));
-
- static Brush()
- {
- Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
- }
///
/// Gets or sets the opacity of the brush.
diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs
index 3ee151389a..17ee14e533 100644
--- a/src/Avalonia.Base/Media/Color.cs
+++ b/src/Avalonia.Base/Media/Color.cs
@@ -25,13 +25,6 @@ namespace Avalonia.Media
{
private const double byteToDouble = 1.0 / 255;
- static Color()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
///
/// Gets the Alpha component of the color.
///
diff --git a/src/Avalonia.Base/Media/Effects/EffectAnimator.cs b/src/Avalonia.Base/Media/Effects/EffectAnimator.cs
index 8353afa6ab..cdf6aa6b63 100644
--- a/src/Avalonia.Base/Media/Effects/EffectAnimator.cs
+++ b/src/Avalonia.Base/Media/Effects/EffectAnimator.cs
@@ -63,8 +63,6 @@ internal class EffectAnimator : Animator
if(s_Registered)
return;
s_Registered = true;
- Animation.RegisterAnimator(prop =>
- typeof(IEffect).IsAssignableFrom(prop.PropertyType));
}
}
diff --git a/src/Avalonia.Base/Media/EllipseGeometry.cs b/src/Avalonia.Base/Media/EllipseGeometry.cs
index 8211855324..bb1263d8d5 100644
--- a/src/Avalonia.Base/Media/EllipseGeometry.cs
+++ b/src/Avalonia.Base/Media/EllipseGeometry.cs
@@ -56,6 +56,10 @@ namespace Avalonia.Media
///
/// Gets or sets a rect that defines the bounds of the ellipse.
///
+ ///
+ /// When set, this takes priority over the other properties that define an
+ /// ellipse using a center point and X/Y-axis radii.
+ ///
public Rect Rect
{
get => GetValue(RectProperty);
@@ -65,6 +69,10 @@ namespace Avalonia.Media
///
/// Gets or sets a double that defines the radius in the X-axis of the ellipse.
///
+ ///
+ /// In order for this property to be used, must not be set
+ /// (equal to the default value).
+ ///
public double RadiusX
{
get => GetValue(RadiusXProperty);
@@ -74,6 +82,10 @@ namespace Avalonia.Media
///
/// Gets or sets a double that defines the radius in the Y-axis of the ellipse.
///
+ ///
+ /// In order for this property to be used, must not be set
+ /// (equal to the default value).
+ ///
public double RadiusY
{
get => GetValue(RadiusYProperty);
@@ -83,6 +95,10 @@ namespace Avalonia.Media
///
/// Gets or sets a point that defines the center of the ellipse.
///
+ ///
+ /// In order for this property to be used, must not be set
+ /// (equal to the default value).
+ ///
public Point Center
{
get => GetValue(CenterProperty);
@@ -92,11 +108,34 @@ namespace Avalonia.Media
///
public override Geometry Clone()
{
- return new EllipseGeometry(Rect);
+ // Note that the ellipse properties are used in two modes:
+ //
+ // 1. Rect-only Mode:
+ // Directly set the rectangle bounds the ellipse will fill
+ //
+ // 2. Center + Radii Mode:
+ // Set a center-point and then X/Y-axis radii that are used to
+ // calculate the rectangle bounds the ellipse will fill.
+ // This is the only mode supported by WPF.
+ //
+ // Rendering the ellipse will only ever use one of these two modes
+ // based on if the Rect property is set (not equal to default).
+ //
+ // This means it would normally be fine to copy ONLY the Rect property
+ // when it is set. However, while it would render the same, it isn't
+ // a true clone. We want to include all the properties here regardless
+ // of the rendering mode that will eventually be used.
+ return new EllipseGeometry()
+ {
+ Rect = Rect,
+ RadiusX = RadiusX,
+ RadiusY = RadiusY,
+ Center = Center,
+ };
}
///
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService();
diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index 4425147098..17d1984286 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -151,8 +151,13 @@ namespace Avalonia.Media
}
}
+ if(typeface.FontFamily == DefaultFontFamily)
+ {
+ return false;
+ }
+
//Nothing was found so use the default
- return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
+ return TryGetGlyphTypeface(new Typeface(FontFamily.DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
}
///
diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs
index 0d2311eafc..a66cd616a3 100644
--- a/src/Avalonia.Base/Media/Geometry.cs
+++ b/src/Avalonia.Base/Media/Geometry.cs
@@ -28,6 +28,11 @@ namespace Avalonia.Media
TransformProperty.Changed.AddClassHandler((x,e) => x.TransformChanged(e));
}
+ internal Geometry()
+ {
+
+ }
+
///
/// Raised when the geometry changes.
///
@@ -134,7 +139,7 @@ namespace Avalonia.Media
/// Creates the platform implementation of the geometry, without the transform applied.
///
///
- protected abstract IGeometryImpl? CreateDefiningGeometry();
+ private protected abstract IGeometryImpl? CreateDefiningGeometry();
///
/// Invalidates the platform implementation of the geometry.
diff --git a/src/Avalonia.Base/Media/GeometryGroup.cs b/src/Avalonia.Base/Media/GeometryGroup.cs
index 3e61413919..20bd297fc1 100644
--- a/src/Avalonia.Base/Media/GeometryGroup.cs
+++ b/src/Avalonia.Base/Media/GeometryGroup.cs
@@ -1,8 +1,6 @@
using Avalonia.Metadata;
using Avalonia.Platform;
-#nullable enable
-
namespace Avalonia.Media
{
///
@@ -72,7 +70,7 @@ namespace Avalonia.Media
newChildren.Parent = this;
}
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
if (_children.Count > 0)
{
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 350d8817f1..fcb2cec733 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/src/Avalonia.Base/Media/GlyphRun.cs
@@ -424,13 +424,13 @@ namespace Avalonia.Media
///
public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{
+ var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
+
if (characterHit.TrailingLength != 0)
{
- return new CharacterHit(characterHit.FirstCharacterIndex);
+ return previousCharacterHit;
}
- var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
-
return new CharacterHit(previousCharacterHit.FirstCharacterIndex);
}
@@ -643,12 +643,13 @@ namespace Avalonia.Media
lastCluster = _glyphInfos[_glyphInfos.Count - 1].GlyphCluster;
}
+ var isReversed = firstCluster > lastCluster;
+
if (!IsLeftToRight)
{
(lastCluster, firstCluster) = (firstCluster, lastCluster);
}
- var isReversed = firstCluster > lastCluster;
var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d;
@@ -766,15 +767,13 @@ namespace Avalonia.Media
if (!charactersSpan.IsEmpty)
{
- var characterIndex = 0;
+ var characterIndex = charactersSpan.Length - 1;
for (var i = 0; i < _glyphInfos.Count; i++)
{
var currentCluster = _glyphInfos[i].GlyphCluster;
var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
- characterIndex += characterLength;
-
if (!codepoint.IsWhiteSpace)
{
break;
@@ -784,9 +783,9 @@ namespace Avalonia.Media
var j = i;
- while (j - 1 >= 0)
+ while (j + 1 < _glyphInfos.Count)
{
- var nextCluster = _glyphInfos[--j].GlyphCluster;
+ var nextCluster = _glyphInfos[++j].GlyphCluster;
if (currentCluster == nextCluster)
{
@@ -798,6 +797,8 @@ namespace Avalonia.Media
break;
}
+ characterIndex -= clusterLength;
+
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
diff --git a/src/Avalonia.Base/Media/LineGeometry.cs b/src/Avalonia.Base/Media/LineGeometry.cs
index 6ac92ea33b..ced208e30b 100644
--- a/src/Avalonia.Base/Media/LineGeometry.cs
+++ b/src/Avalonia.Base/Media/LineGeometry.cs
@@ -68,7 +68,7 @@ namespace Avalonia.Media
}
///
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService();
diff --git a/src/Avalonia.Base/Media/MediaContext.Clock.cs b/src/Avalonia.Base/Media/MediaContext.Clock.cs
index dc2a39a822..ea8ac13e06 100644
--- a/src/Avalonia.Base/Media/MediaContext.Clock.cs
+++ b/src/Avalonia.Base/Media/MediaContext.Clock.cs
@@ -4,6 +4,7 @@ using System.Diagnostics;
using Avalonia.Animation;
using Avalonia.Reactive;
using Avalonia.Threading;
+using Avalonia.Utilities;
namespace Avalonia.Media;
@@ -17,8 +18,12 @@ internal partial class MediaContext
{
private readonly MediaContext _parent;
private List> _observers = new();
- public bool HasNewSubscriptions { get; set; }
- public bool HasSubscriptions => _observers.Count > 0;
+ private List> _newObservers = new();
+ private Queue> _queuedAnimationFrames = new();
+ private Queue> _queuedAnimationFramesNext = new();
+ private TimeSpan _currentAnimationTimestamp;
+ public bool HasNewSubscriptions => _newObservers.Count > 0;
+ public bool HasSubscriptions => _observers.Count > 0 || _queuedAnimationFrames.Count > 0;
public MediaContextClock(MediaContext parent)
{
@@ -29,19 +34,41 @@ internal partial class MediaContext
{
_parent.ScheduleRender(false);
Dispatcher.UIThread.VerifyAccess();
- HasNewSubscriptions = true;
_observers.Add(observer);
+ _newObservers.Add(observer);
return Disposable.Create(() =>
{
Dispatcher.UIThread.VerifyAccess();
_observers.Remove(observer);
});
}
+
+ public void RequestAnimationFrame(Action action)
+ {
+ _parent.ScheduleRender(false);
+ _queuedAnimationFrames.Enqueue(action);
+ }
public void Pulse(TimeSpan now)
{
+ _newObservers.Clear();
+ _currentAnimationTimestamp = now;
+
+ // We are swapping the queues before enumeration
+ (_queuedAnimationFrames, _queuedAnimationFramesNext) = (_queuedAnimationFramesNext, _queuedAnimationFrames);
+ var animationFrames = _queuedAnimationFramesNext;
+ while (animationFrames.TryDequeue(out var callback))
+ callback(now);
+
foreach (var observer in _observers.ToArray())
- observer.OnNext(now);
+ observer.OnNext(_currentAnimationTimestamp);
+ }
+
+ public void PulseNewSubscriptions()
+ {
+ foreach (var observer in _newObservers.ToArray())
+ observer.OnNext(_currentAnimationTimestamp);
+ _newObservers.Clear();
}
public PlayState PlayState
@@ -50,4 +77,6 @@ internal partial class MediaContext
set => throw new InvalidOperationException();
}
}
+
+ public void RequestAnimationFrame(Action action) => _clock.RequestAnimationFrame(action);
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/MediaContext.Compositor.cs b/src/Avalonia.Base/Media/MediaContext.Compositor.cs
index 9bdd77960d..4ddc2ea9eb 100644
--- a/src/Avalonia.Base/Media/MediaContext.Compositor.cs
+++ b/src/Avalonia.Base/Media/MediaContext.Compositor.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
@@ -78,7 +79,7 @@ partial class MediaContext
// Nothing to do, and there are no pending commits
return false;
- foreach (var c in _requestedCommits)
+ foreach (var c in _requestedCommits.ToArray())
CommitCompositor(c);
_requestedCommits.Clear();
diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs
index c261dd80b8..0a290052c7 100644
--- a/src/Avalonia.Base/Media/MediaContext.cs
+++ b/src/Avalonia.Base/Media/MediaContext.cs
@@ -131,12 +131,11 @@ internal partial class MediaContext : ICompositorScheduler
// We are doing several iterations when it happens
for (var c = 0; c < 10; c++)
{
- _clock.HasNewSubscriptions = false;
FireInvokeOnRenderCallbacks();
if (_clock.HasNewSubscriptions)
{
- _clock.Pulse(now);
+ _clock.PulseNewSubscriptions();
continue;
}
@@ -212,7 +211,12 @@ internal partial class MediaContext : ICompositorScheduler
}
while (count > 0);
}
-
+
+ ///
+ /// Executes the callback in the next iteration of the current UI-thread
+ /// render loop / layout pass that.
+ ///
+ ///
public void BeginInvokeOnRender(Action callback)
{
if (_invokeOnRenderCallbacks == null)
@@ -224,4 +228,4 @@ internal partial class MediaContext : ICompositorScheduler
if (!_isRendering)
ScheduleRender(true);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Media/PathGeometry.cs b/src/Avalonia.Base/Media/PathGeometry.cs
index 8292afde7e..bdfbfadce4 100644
--- a/src/Avalonia.Base/Media/PathGeometry.cs
+++ b/src/Avalonia.Base/Media/PathGeometry.cs
@@ -43,7 +43,7 @@ namespace Avalonia.Media
///
/// The s.
///
- public static new PathGeometry Parse(string pathData)
+ public new static PathGeometry Parse(string pathData)
{
var pathGeometry = new PathGeometry();
@@ -81,7 +81,7 @@ namespace Avalonia.Media
set { SetValue(FillRuleProperty, value); }
}
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var figures = Figures;
diff --git a/src/Avalonia.Base/Media/PlatformGeometry.cs b/src/Avalonia.Base/Media/PlatformGeometry.cs
index f25a14540f..e1488a8229 100644
--- a/src/Avalonia.Base/Media/PlatformGeometry.cs
+++ b/src/Avalonia.Base/Media/PlatformGeometry.cs
@@ -2,7 +2,7 @@
namespace Avalonia.Media
{
- internal class PlatformGeometry : Geometry
+ internal sealed class PlatformGeometry : Geometry
{
private readonly IGeometryImpl _geometryImpl;
@@ -16,7 +16,7 @@ namespace Avalonia.Media
return new PlatformGeometry(_geometryImpl);
}
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected override IGeometryImpl? CreateDefiningGeometry()
{
return _geometryImpl;
}
diff --git a/src/Avalonia.Base/Media/PolylineGeometry.cs b/src/Avalonia.Base/Media/PolylineGeometry.cs
index b0229b6455..47cf2f48a4 100644
--- a/src/Avalonia.Base/Media/PolylineGeometry.cs
+++ b/src/Avalonia.Base/Media/PolylineGeometry.cs
@@ -74,7 +74,7 @@ namespace Avalonia.Media
return new PolylineGeometry(Points, IsFilled);
}
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService();
var geometry = factory.CreateStreamGeometry();
diff --git a/src/Avalonia.Base/Media/RectangleGeometry.cs b/src/Avalonia.Base/Media/RectangleGeometry.cs
index 0bf9eb5664..01771242a7 100644
--- a/src/Avalonia.Base/Media/RectangleGeometry.cs
+++ b/src/Avalonia.Base/Media/RectangleGeometry.cs
@@ -47,7 +47,7 @@ namespace Avalonia.Media
///
public override Geometry Clone() => new RectangleGeometry(Rect);
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService();
diff --git a/src/Avalonia.Base/Media/StreamGeometry.cs b/src/Avalonia.Base/Media/StreamGeometry.cs
index fb79488e0f..9969376896 100644
--- a/src/Avalonia.Base/Media/StreamGeometry.cs
+++ b/src/Avalonia.Base/Media/StreamGeometry.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Media
///
/// The string.
/// A .
- public static new StreamGeometry Parse(string s)
+ public new static StreamGeometry Parse(string s)
{
var streamGeometry = new StreamGeometry();
@@ -62,7 +62,7 @@ namespace Avalonia.Media
}
///
- protected override IGeometryImpl? CreateDefiningGeometry()
+ private protected override IGeometryImpl? CreateDefiningGeometry()
{
if (_impl == null)
{
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
index a609800fb8..d2198a2cbf 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
@@ -684,7 +684,9 @@ namespace Avalonia.Media.TextFormatting
var textRuns = new TextRun[] { new ShapedTextRun(shapedBuffer, properties) };
var line = new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection);
+
line.FinalizeLine();
+
return line;
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
index 4ccb3f6a37..b6b6d11a49 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
@@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
///
/// Gets the text spacing.
///
- public double LetterSpacing => _paragraphProperties.LetterSpacing;
+ public double LetterSpacing => _paragraphProperties.LetterSpacing;
///
/// Gets the text lines.
@@ -271,11 +271,13 @@ namespace Avalonia.Media.TextFormatting
var currentY = 0.0;
- foreach (var textLine in _textLines)
+ for (var i = 0; i < _textLines.Length; i++)
{
+ var textLine = _textLines[i];
+
var end = textLine.FirstTextSourceIndex + textLine.Length;
- if (end <= textPosition && end < _textSourceLength)
+ if (end <= textPosition && i + 1 < _textLines.Length)
{
currentY += textLine.Height;
@@ -511,7 +513,7 @@ namespace Avalonia.Media.TextFormatting
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
- UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
+ UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
return new TextLine[] { textLine };
@@ -638,13 +640,13 @@ namespace Avalonia.Media.TextFormatting
}
private void UpdateMetrics(
- TextLine currentLine,
- ref double lineStartOfLongestLine,
- ref Point origin,
- ref bool first,
+ TextLine currentLine,
+ ref double lineStartOfLongestLine,
+ ref Point origin,
+ ref bool first,
ref double accBlackBoxLeft,
- ref double accBlackBoxTop,
- ref double accBlackBoxRight,
+ ref double accBlackBoxTop,
+ ref double accBlackBoxRight,
ref double accBlackBoxBottom)
{
var blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index c2ec78e187..a2936f1857 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Media.TextFormatting
internal static Comparer TextBoundsComparer { get; } =
Comparer.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left));
- private IReadOnlyList? _indexedTextRuns;
+ internal IReadOnlyList? _indexedTextRuns;
private readonly TextRun[] _textRuns;
private readonly double _paragraphWidth;
private readonly TextParagraphProperties _paragraphProperties;
@@ -204,7 +204,7 @@ namespace Avalonia.Media.TextFormatting
if (_paragraphProperties.FlowDirection == FlowDirection.RightToLeft)
{
- currentPosition = Length - firstRun.Length;
+ currentPosition += lineLength - firstRun.Length;
}
return GetRunCharacterHit(firstRun, currentPosition, 0);
@@ -216,7 +216,7 @@ namespace Avalonia.Media.TextFormatting
if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight)
{
- currentPosition = lineLength - lastRun.Length;
+ currentPosition += lineLength - lastRun.Length;
}
return GetRunCharacterHit(lastRun, currentPosition, distance);
@@ -371,14 +371,16 @@ namespace Avalonia.Media.TextFormatting
IndexedTextRun currentIndexedRun = _indexedTextRuns[i];
- while(currentIndexedRun.TextSourceCharacterIndex != currentPosition)
+ while (currentIndexedRun.TextSourceCharacterIndex != currentPosition)
{
- if(i + 1 < _indexedTextRuns.Count)
+ if (i + 1 == _indexedTextRuns.Count)
{
- i++;
-
- currentIndexedRun = _indexedTextRuns[i];
+ break;
}
+
+ i++;
+
+ currentIndexedRun = _indexedTextRuns[i];
}
return currentIndexedRun;
@@ -430,7 +432,7 @@ namespace Avalonia.Media.TextFormatting
if (currentTextRun == null)
{
- return 0;
+ return Start;
}
var directionalWidth = 0.0;
@@ -510,38 +512,45 @@ namespace Avalonia.Media.TextFormatting
///
public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
{
- if (_textRuns.Length == 0)
+ if (_textRuns.Length == 0 || _indexedTextRuns is null)
{
return new CharacterHit();
}
- if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit))
- {
- return nextCharacterHit;
- }
-
- var lastTextPosition = FirstTextSourceIndex + Length;
+ var currentCharacterrHit = characterHit;
+ var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
- // Can't move, we're after the last character
- var runIndex = GetRunIndexAtCharacterIndex(lastTextPosition, LogicalDirection.Forward, out var currentPosition);
+ var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Forward, out var currentPosition);
- var currentRun = _textRuns[runIndex];
+ var nextCharacterHit = characterHit;
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
- nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(characterHit);
+ var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster - characterHit.TrailingLength);
+
+ if (offset > 0)
+ {
+ currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
+ }
+
+ nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(currentCharacterrHit);
+
+ if (offset > 0)
+ {
+ nextCharacterHit = new CharacterHit(nextCharacterHit.FirstCharacterIndex + offset, nextCharacterHit.TrailingLength);
+ }
break;
}
- default:
+ case TextRun:
{
nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
break;
}
}
- if (characterHit.FirstCharacterIndex + characterHit.TrailingLength == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
+ if (characterIndex == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
{
return characterHit;
}
@@ -552,17 +561,75 @@ namespace Avalonia.Media.TextFormatting
///
public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{
- if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit))
+ if (_textRuns.Length == 0 || _indexedTextRuns is null)
+ {
+ return new CharacterHit();
+ }
+
+ if (characterHit.TrailingLength > 0 && characterHit.FirstCharacterIndex <= FirstTextSourceIndex)
+ {
+ return new CharacterHit(FirstTextSourceIndex);
+ }
+
+ var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+
+ if (characterIndex <= FirstTextSourceIndex)
{
- return previousCharacterHit;
+ return new CharacterHit(FirstTextSourceIndex);
+ }
+
+ var currentCharacterrHit = characterHit;
+
+ var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
+
+ if (currentPosition == characterHit.FirstCharacterIndex)
+ {
+ currentRun = GetRunAtCharacterIndex(characterHit.FirstCharacterIndex, LogicalDirection.Backward, out currentPosition);
+ }
+
+ var previousCharacterHit = characterHit;
+
+ switch (currentRun)
+ {
+ case ShapedTextRun shapedRun:
+ {
+ var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster);
+
+ if (offset > 0)
+ {
+ currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
+ }
+
+ previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(currentCharacterrHit);
+
+ if (offset > 0)
+ {
+ previousCharacterHit = new CharacterHit(previousCharacterHit.FirstCharacterIndex + offset, previousCharacterHit.TrailingLength);
+ }
+ break;
+ }
+ case TextRun:
+ {
+ if (characterHit.TrailingLength > 0)
+ {
+ previousCharacterHit = new CharacterHit(currentPosition, currentRun.Length);
+
+ }
+ else
+ {
+ previousCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
+ }
+
+ break;
+ }
}
- if (characterHit.FirstCharacterIndex <= FirstTextSourceIndex)
+ if (characterIndex == previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength)
{
- characterHit = new CharacterHit(FirstTextSourceIndex);
+ return characterHit;
}
- return characterHit; // Can't move, we're before the first character
+ return previousCharacterHit;
}
///
@@ -584,6 +651,8 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex;
var remainingLength = textLength;
+ TextBounds? lastBounds = null;
+
static FlowDirection GetDirection(TextRun textRun, FlowDirection currentDirection)
{
if (textRun is ShapedTextRun shapedTextRun)
@@ -604,12 +673,14 @@ namespace Avalonia.Media.TextFormatting
while (currentIndexedRun.TextSourceCharacterIndex != currentPosition)
{
- if (i + 1 < _indexedTextRuns.Count)
+ if (i + 1 == _indexedTextRuns.Count)
{
- i++;
-
- currentIndexedRun = _indexedTextRuns[i];
+ break;
}
+
+ i++;
+
+ currentIndexedRun = _indexedTextRuns[i];
}
return currentIndexedRun;
@@ -632,6 +703,40 @@ namespace Avalonia.Media.TextFormatting
return distance;
}
+ bool TryMergeWithLastBounds(TextBounds currentBounds, TextBounds lastBounds)
+ {
+ if (currentBounds.FlowDirection != lastBounds.FlowDirection)
+ {
+ return false;
+ }
+
+ if (currentBounds.Rectangle.Left == lastBounds.Rectangle.Right)
+ {
+ foreach (var runBounds in currentBounds.TextRunBounds)
+ {
+ lastBounds.TextRunBounds.Add(runBounds);
+ }
+
+ lastBounds.Rectangle = lastBounds.Rectangle.Union(currentBounds.Rectangle);
+
+ return true;
+ }
+
+ if (currentBounds.Rectangle.Right == lastBounds.Rectangle.Left)
+ {
+ for (int i = 0; i < currentBounds.TextRunBounds.Count; i++)
+ {
+ lastBounds.TextRunBounds.Insert(i, currentBounds.TextRunBounds[i]);
+ }
+
+ lastBounds.Rectangle = lastBounds.Rectangle.Union(currentBounds.Rectangle);
+
+ return true;
+ }
+
+ return false;
+ }
+
while (remainingLength > 0 && currentPosition < FirstTextSourceIndex + Length)
{
var currentIndexedRun = FindIndexedRun();
@@ -667,67 +772,21 @@ namespace Avalonia.Media.TextFormatting
directionalWidth = currentDrawable.Size.Width;
}
- if (currentTextRun is not TextEndOfLine)
- {
- if (currentDirection == FlowDirection.LeftToRight)
- {
- // Find consecutive runs of same direction
- for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++)
- {
- var nextRun = _textRuns[lastRunIndex + 1];
-
- var nextDirection = GetDirection(nextRun, currentDirection);
-
- if (currentDirection != nextDirection)
- {
- break;
- }
-
- if (nextRun is DrawableTextRun nextDrawable)
- {
- directionalWidth += nextDrawable.Size.Width;
- }
- }
- }
- else
- {
- // Find consecutive runs of same direction
- for (; firstRunIndex - 1 > 0; firstRunIndex--)
- {
- var previousRun = _textRuns[firstRunIndex - 1];
-
- var previousDirection = GetDirection(previousRun, currentDirection);
-
- if (currentDirection != previousDirection)
- {
- break;
- }
-
- if (previousRun is DrawableTextRun previousDrawable)
- {
- directionalWidth += previousDrawable.Size.Width;
-
- currentX -= previousDrawable.Size.Width;
- }
- }
- }
- }
-
int coveredLength;
- TextBounds? textBounds;
+ TextBounds? currentBounds;
switch (currentDirection)
{
case FlowDirection.RightToLeft:
{
- textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex,
+ currentBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex,
currentPosition, remainingLength, out coveredLength, out currentPosition);
break;
}
default:
{
- textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex,
+ currentBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex,
currentPosition, remainingLength, out coveredLength, out currentPosition);
break;
@@ -736,7 +795,18 @@ namespace Avalonia.Media.TextFormatting
if (coveredLength > 0)
{
- result.Add(textBounds);
+ if (lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds))
+ {
+ currentBounds = lastBounds;
+
+ result[result.Count - 1] = currentBounds;
+ }
+ else
+ {
+ result.Add(currentBounds);
+ }
+
+ lastBounds = currentBounds;
remainingLength -= coveredLength;
}
@@ -997,168 +1067,14 @@ namespace Avalonia.Media.TextFormatting
public void FinalizeLine()
{
+ _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
+
_textLineMetrics = CreateLineMetrics();
if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine)
{
_textLineBreak = new TextLineBreak(textEndOfLine);
}
-
- _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
- }
-
- ///
- /// Tries to find the next character hit.
- ///
- /// The current character hit.
- /// The next character hit.
- ///
- private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit)
- {
- nextCharacterHit = characterHit;
-
- var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
- var lastCodepointIndex = FirstTextSourceIndex + Length;
-
- if (codepointIndex >= lastCodepointIndex)
- {
- return false; // Cannot go forward anymore
- }
-
- if (codepointIndex < FirstTextSourceIndex)
- {
- codepointIndex = FirstTextSourceIndex;
- }
-
- var runIndex = GetRunIndexAtCharacterIndex(codepointIndex, LogicalDirection.Forward, out var currentPosition);
-
- while (runIndex < _textRuns.Length)
- {
- var currentRun = _textRuns[runIndex];
-
- switch (currentRun)
- {
- case ShapedTextRun shapedRun:
- {
- var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
-
- var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == FirstTextSourceIndex + Length;
-
- if (isAtEnd && !shapedRun.GlyphRun.IsLeftToRight)
- {
- nextCharacterHit = foundCharacterHit;
-
- return true;
- }
-
- nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
- foundCharacterHit :
- new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
-
- if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
- {
- return true;
- }
-
- break;
- }
- default:
- {
- var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
-
- if (textPosition == currentPosition)
- {
- nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
-
- return true;
- }
-
- break;
- }
- }
-
- currentPosition += currentRun.Length;
- runIndex++;
- }
-
- return false;
- }
-
- ///
- /// Tries to find the previous character hit.
- ///
- /// The current character hit.
- /// The previous character hit.
- ///
- private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit)
- {
- var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
-
- if (characterIndex == FirstTextSourceIndex)
- {
- previousCharacterHit = new CharacterHit(FirstTextSourceIndex);
-
- return true;
- }
-
- previousCharacterHit = characterHit;
-
- if (characterIndex < FirstTextSourceIndex)
- {
- return false; // Cannot go backward anymore.
- }
-
- var runIndex = GetRunIndexAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
-
- while (runIndex >= 0)
- {
- var currentRun = _textRuns[runIndex];
-
- switch (currentRun)
- {
- case ShapedTextRun shapedRun:
- {
- var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
-
- if (foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength < characterIndex)
- {
- previousCharacterHit = foundCharacterHit;
-
- return true;
- }
-
- var previousPosition = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength;
-
- if (foundCharacterHit.TrailingLength > 0 && previousPosition == characterIndex)
- {
- previousCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex);
- }
-
- if (previousCharacterHit != characterHit)
- {
- return true;
- }
-
- break;
- }
- default:
- {
- if (characterIndex == currentPosition + currentRun.Length)
- {
- previousCharacterHit = new CharacterHit(currentPosition);
-
- return true;
- }
-
- break;
- }
- }
-
- currentPosition -= currentRun.Length;
- runIndex--;
- }
-
- return false;
}
///
@@ -1168,15 +1084,23 @@ namespace Avalonia.Media.TextFormatting
/// The logical direction.
/// The text position of the found run index.
/// The text run index.
- private int GetRunIndexAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition)
+ private TextRun? GetRunAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition)
{
var runIndex = 0;
textPosition = FirstTextSourceIndex;
+
+ if (_indexedTextRuns is null)
+ {
+ return null;
+ }
+
+ TextRun? currentRun = null;
TextRun? previousRun = null;
- while (runIndex < _textRuns.Length)
+ while (runIndex < _indexedTextRuns.Count)
{
- var currentRun = _textRuns[runIndex];
+ var indexedRun = _indexedTextRuns[runIndex];
+ currentRun = indexedRun.TextRun;
switch (currentRun)
{
@@ -1184,64 +1108,49 @@ namespace Avalonia.Media.TextFormatting
{
var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster;
- if (firstCluster > codepointIndex)
- {
- break;
- }
-
- if (previousRun is ShapedTextRun previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight)
- {
- if (shapedRun.ShapedBuffer.IsLeftToRight)
- {
- if (firstCluster >= codepointIndex)
- {
- return --runIndex;
- }
- }
- else
- {
- if (codepointIndex > firstCluster + currentRun.Length)
- {
- return --runIndex;
- }
- }
- }
+ firstCluster += Math.Max(0, indexedRun.TextSourceCharacterIndex - firstCluster);
if (direction == LogicalDirection.Forward)
{
- if (codepointIndex >= firstCluster && codepointIndex <= firstCluster + currentRun.Length)
+ if (codepointIndex >= firstCluster && codepointIndex < firstCluster + currentRun.Length)
{
- return runIndex;
+ return currentRun;
}
}
else
{
- if (codepointIndex > firstCluster &&
- codepointIndex <= firstCluster + currentRun.Length)
+ if (previousRun is not null && previousRun is not ShapedTextRun && codepointIndex == textPosition + firstCluster)
{
- return runIndex;
+ textPosition -= previousRun.Length;
+
+ return previousRun;
+ }
+
+ if (codepointIndex > firstCluster && codepointIndex <= firstCluster + currentRun.Length)
+ {
+ return currentRun;
}
}
if (runIndex + 1 >= _textRuns.Length)
{
- return runIndex;
+ return currentRun;
}
textPosition += currentRun.Length;
break;
}
- default:
+ case TextRun:
{
if (codepointIndex == textPosition)
{
- return runIndex;
+ return currentRun;
}
if (runIndex + 1 >= _textRuns.Length)
{
- return runIndex;
+ return currentRun;
}
textPosition += currentRun.Length;
@@ -1252,10 +1161,11 @@ namespace Avalonia.Media.TextFormatting
}
runIndex++;
+
previousRun = currentRun;
}
- return runIndex;
+ return currentRun;
}
private TextLineMetrics CreateLineMetrics()
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs
index 3406432ce7..f418d4e14a 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs
@@ -687,7 +687,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
///
/// This method resolves the sos and eos values for the run
/// and adds the run to the list
- /// ///
+ ///
/// The index of the start of the run (in x9 removed units)
/// The length of the run (in x9 removed units)
/// The level of the run
diff --git a/src/Avalonia.Base/Media/Transform.cs b/src/Avalonia.Base/Media/Transform.cs
index 1ac81808a1..16ae137b39 100644
--- a/src/Avalonia.Base/Media/Transform.cs
+++ b/src/Avalonia.Base/Media/Transform.cs
@@ -15,12 +15,6 @@ namespace Avalonia.Media
///
public abstract class Transform : Animatable, IMutableTransform, ICompositionRenderResource, ICompositorSerializable
{
- static Transform()
- {
- Animation.Animation.RegisterAnimator(prop =>
- typeof(ITransform).IsAssignableFrom(prop.OwnerType));
- }
-
internal Transform()
{
diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
index 08fcdb50aa..a4d14b0084 100644
--- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
@@ -1,12 +1,16 @@
using System;
using Avalonia.Input;
+using Avalonia.Input.Platform;
using Avalonia.Media;
+using Avalonia.Metadata;
+using Avalonia.VisualTree;
namespace Avalonia.Platform
{
///
/// A default implementation of for platforms.
///
+ [PrivateApi]
public class DefaultPlatformSettings : IPlatformSettings
{
public virtual Size GetTapSize(PointerType type)
@@ -28,7 +32,10 @@ namespace Avalonia.Platform
public virtual TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
public virtual TimeSpan HoldWaitDuration => TimeSpan.FromMilliseconds(300);
-
+
+ public PlatformHotkeyConfiguration HotkeyConfiguration =>
+ AvaloniaLocator.Current.GetRequiredService();
+
public virtual PlatformColorValues GetColorValues()
{
return new PlatformColorValues
diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
index a0cd2a8dcd..b52378d3b3 100644
--- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
@@ -140,7 +140,7 @@ namespace Avalonia.Platform
void PopOpacity();
///
- /// Pushes an opacity mask
+ /// Pushes an opacity mask.
///
void PushOpacityMask(IBrush mask, Rect bounds);
@@ -161,9 +161,7 @@ namespace Avalonia.Platform
void PopGeometryClip();
///
-
- ///
- /// Attempts to get an optional feature from the drawing context implementation
+ /// Attempts to get an optional feature from the drawing context implementation.
///
object? GetFeature(Type t);
}
@@ -177,7 +175,7 @@ namespace Avalonia.Platform
public static class DrawingContextImplExtensions
{
///
- /// Attempts to get an optional feature from the drawing context implementation
+ /// Attempts to get an optional feature from the drawing context implementation.
///
public static T? GetFeature(this IDrawingContextImpl context) where T : class =>
(T?)context.GetFeature(typeof(T));
@@ -186,13 +184,13 @@ namespace Avalonia.Platform
public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl
{
///
- /// Does optimized blit with Src blend mode
+ /// Does optimized blit with Src blend mode.
///
///
void Blit(IDrawingContextImpl context);
///
- /// Returns true if layer supports optimized blit
+ /// Returns true if layer supports optimized blit.
///
bool CanBlit { get; }
}
diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
index 6f62c3be1d..57fedb3d69 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Platform
/// Creates an ellipse geometry implementation.
///
/// The bounds of the ellipse.
- /// An ellipse geometry..
+ /// An ellipse geometry.
IGeometryImpl CreateEllipseGeometry(Rect rect);
///
diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs
index b167cf0edc..46980c6d51 100644
--- a/src/Avalonia.Base/Platform/IPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs
@@ -1,10 +1,15 @@
using System;
using Avalonia.Input;
+using Avalonia.Input.Platform;
using Avalonia.Metadata;
namespace Avalonia.Platform
{
- [Unstable]
+ ///
+ /// The interface represents a contract for accessing platform-specific settings and information.
+ /// Some of these settings might be changed by used globally in the OS in runtime.
+ ///
+ [NotClientImplementable]
public interface IPlatformSettings
{
///
@@ -33,6 +38,11 @@ namespace Avalonia.Platform
///
TimeSpan HoldWaitDuration { get; }
+ ///
+ /// Get a configuration for platform-specific hotkeys in an Avalonia application.
+ ///
+ PlatformHotkeyConfiguration HotkeyConfiguration { get; }
+
///
/// Gets current system color values including dark mode and accent colors.
///
diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs
index 64b504c479..c6efadc99a 100644
--- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs
+++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs
@@ -3,24 +3,13 @@ using Avalonia.Metadata;
namespace Avalonia.Platform
{
- [Unstable]
+ [PrivateApi]
public interface IRuntimePlatform
{
- IDisposable StartSystemTimer(TimeSpan interval, Action tick);
RuntimePlatformInfo GetRuntimeInfo();
- IUnmanagedBlob AllocBlob(int size);
}
- [Unstable]
- public interface IUnmanagedBlob : IDisposable
- {
- IntPtr Address { get; }
- int Size { get; }
- bool IsDisposed { get; }
-
- }
-
- [Unstable]
+ [PrivateApi]
public record struct RuntimePlatformInfo
{
public FormFactorType FormFactor => IsDesktop ? FormFactorType.Desktop :
@@ -29,7 +18,6 @@ namespace Avalonia.Platform
public bool IsMobile { get; set; }
}
- [Unstable]
public enum FormFactorType
{
Unknown,
diff --git a/src/Avalonia.Base/Platform/Internal/DynLoader.cs b/src/Avalonia.Base/Platform/Internal/DynLoader.cs
deleted file mode 100644
index 07903669b1..0000000000
--- a/src/Avalonia.Base/Platform/Internal/DynLoader.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using Avalonia.Platform.Interop;
-
-// ReSharper disable InconsistentNaming
-namespace Avalonia.Platform.Internal
-{
- class UnixLoader : IDynamicLibraryLoader
- {
- static class LinuxImports
- {
- [DllImport("libdl.so.2")]
- private static extern IntPtr dlopen(string path, int flags);
-
- [DllImport("libdl.so.2")]
- private static extern IntPtr dlsym(IntPtr handle, string symbol);
-
- [DllImport("libdl.so.2")]
- private static extern IntPtr dlerror();
-
- public static void Init()
- {
- DlOpen = dlopen;
- DlSym = dlsym;
- DlError = dlerror;
- }
- }
-
- static class OsXImports
- {
- [DllImport("/usr/lib/libSystem.dylib")]
- private static extern IntPtr dlopen(string path, int flags);
-
- [DllImport("/usr/lib/libSystem.dylib")]
- private static extern IntPtr dlsym(IntPtr handle, string symbol);
-
- [DllImport("/usr/lib/libSystem.dylib")]
- private static extern IntPtr dlerror();
-
- public static void Init()
- {
- DlOpen = dlopen;
- DlSym = dlsym;
- DlError = dlerror;
- }
-
- }
-
-
- [DllImport("libc")]
- static extern int uname(IntPtr buf);
-
- static UnixLoader()
- {
- var buffer = Marshal.AllocHGlobal(0x1000);
- uname(buffer);
- var unixName = Marshal.PtrToStringAnsi(buffer);
- Marshal.FreeHGlobal(buffer);
- if (unixName == "Darwin")
- OsXImports.Init();
- else
- LinuxImports.Init();
- }
-
- private static Func? DlOpen;
- private static Func? DlSym;
- private static Func? DlError;
- // ReSharper restore InconsistentNaming
-
- static string? DlErrorString() => Marshal.PtrToStringAnsi(DlError!.Invoke());
-
- public IntPtr LoadLibrary(string dll)
- {
- var handle = DlOpen!.Invoke(dll, 1);
- if (handle == IntPtr.Zero)
- throw new DynamicLibraryLoaderException(DlErrorString()!);
- return handle;
- }
-
- public IntPtr GetProcAddress(IntPtr dll, string proc, bool optional)
- {
- var ptr = DlSym!.Invoke(dll, proc);
- if (ptr == IntPtr.Zero && !optional)
- throw new DynamicLibraryLoaderException(DlErrorString()!);
- return ptr;
- }
- }
-
- internal class Win32Loader : IDynamicLibraryLoader
- {
- [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
- private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
-
- [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern IntPtr LoadLibrary(string lpszLib);
-
- IntPtr IDynamicLibraryLoader.LoadLibrary(string dll)
- {
- var handle = LoadLibrary(dll);
- if (handle != IntPtr.Zero)
- return handle;
- var err = Marshal.GetLastWin32Error();
-
- throw new DynamicLibraryLoaderException("Error loading " + dll + " error " + err);
- }
-
- IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional)
- {
- var ptr = GetProcAddress(dll, proc);
- if (ptr == IntPtr.Zero && !optional)
- throw new DynamicLibraryLoaderException("Error " + Marshal.GetLastWin32Error());
- return ptr;
- }
- }
-
-#if NET6_0_OR_GREATER
- internal class Net6Loader : IDynamicLibraryLoader
- {
- public IntPtr LoadLibrary(string dll)
- {
- try
- {
- return NativeLibrary.Load(dll);
- }
- catch (Exception ex)
- {
- throw new DynamicLibraryLoaderException("Error loading " + dll, ex);
- }
- }
-
- public IntPtr GetProcAddress(IntPtr dll, string proc, bool optional)
- {
- try
- {
- if (optional)
- {
- return NativeLibrary.TryGetExport(dll, proc, out var address) ? address : default;
- }
- return NativeLibrary.GetExport(dll, proc);
- }
- catch (Exception ex)
- {
- throw new DynamicLibraryLoaderException("Error " + dll, ex);
- }
- }
- }
-#endif
-
- internal class NotSupportedLoader : IDynamicLibraryLoader
- {
- IntPtr IDynamicLibraryLoader.LoadLibrary(string dll)
- {
- throw new PlatformNotSupportedException();
- }
-
- IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional)
- {
- throw new PlatformNotSupportedException();
- }
- }
-}
diff --git a/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs b/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs
index a56d9ffd1c..fc299dbcec 100644
--- a/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs
+++ b/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs
@@ -6,7 +6,7 @@ using System.Threading;
namespace Avalonia.Platform.Internal;
-internal class UnmanagedBlob : IUnmanagedBlob
+internal class UnmanagedBlob
{
private IntPtr _address;
private readonly object _lock = new object();
diff --git a/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs b/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs
deleted file mode 100644
index 56c50e340d..0000000000
--- a/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using Avalonia.Metadata;
-
-namespace Avalonia.Platform.Interop
-{
- public interface IDynamicLibraryLoader
- {
- IntPtr LoadLibrary(string dll);
- IntPtr GetProcAddress(IntPtr dll, string proc, bool optional);
- }
-
- public class DynamicLibraryLoaderException : Exception
- {
- public DynamicLibraryLoaderException(string message) : base(message)
- {
-
- }
-
- public DynamicLibraryLoaderException(string message, Exception innerException) : base(message, innerException)
- {
-
- }
- }
-}
diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs
index 8352d794d0..7a5c92c774 100644
--- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs
+++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs
@@ -1,26 +1,20 @@
using System;
using System.Threading;
using Avalonia.Compatibility;
+using Avalonia.Metadata;
using Avalonia.Platform.Internal;
namespace Avalonia.Platform
{
+ [PrivateApi]
public class StandardRuntimePlatform : IRuntimePlatform
{
- public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
- {
- return new Timer(_ => tick(), null, interval, interval);
- }
-
- public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(size);
-
private static readonly RuntimePlatformInfo s_info = new()
{
IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(),
IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsIOS()
};
-
-
+
public virtual RuntimePlatformInfo GetRuntimeInfo() => s_info;
}
}
diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs
index 800d9b390f..70919bc477 100644
--- a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs
+++ b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs
@@ -3,27 +3,15 @@ using Avalonia.Compatibility;
using Avalonia.Platform.Internal;
using Avalonia.Platform.Interop;
-namespace Avalonia.Platform
+namespace Avalonia.Platform;
+
+internal static class StandardRuntimePlatformServices
{
- public static class StandardRuntimePlatformServices
+ public static void Register(Assembly? assembly = null)
{
- public static void Register(Assembly? assembly = null)
- {
- var standardPlatform = new StandardRuntimePlatform();
-
- AssetLoader.RegisterResUriParsers();
- AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(standardPlatform)
- .Bind().ToConstant(new StandardAssetLoader(assembly))
- .Bind().ToConstant(
-#if NET6_0_OR_GREATER
- new Net6Loader()
-#else
- OperatingSystemEx.IsWindows() ? (IDynamicLibraryLoader)new Win32Loader()
- : OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux() || OperatingSystemEx.IsAndroid() ? new UnixLoader()
- : new NotSupportedLoader()
-#endif
- );
- }
+ AssetLoader.RegisterResUriParsers();
+ AvaloniaLocator.CurrentMutable
+ .Bind().ToSingleton()
+ .Bind().ToConstant(new StandardAssetLoader(assembly));
}
}
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
index f9f02ec339..0c455ab511 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
@@ -64,7 +64,8 @@ internal class BclStorageFile : IStorageBookmarkFile
public Task OpenWriteAsync()
{
- return Task.FromResult(FileInfo.OpenWrite());
+ var stream = new FileStream(FileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.Write);
+ return Task.FromResult(stream);
}
public virtual Task SaveBookmarkAsync()
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
index 55aac6f3fa..76323eb900 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
@@ -10,16 +10,19 @@ internal static class StorageProviderHelpers
{
public static IStorageItem? TryCreateBclStorageItem(string path)
{
- var directory = new DirectoryInfo(path);
- if (directory.Exists)
+ if (!string.IsNullOrWhiteSpace(path))
{
- return new BclStorageFolder(directory);
- }
-
- var file = new FileInfo(path);
- if (file.Exists)
- {
- return new BclStorageFile(file);
+ var directory = new DirectoryInfo(path);
+ if (directory.Exists)
+ {
+ return new BclStorageFolder(directory);
+ }
+
+ var file = new FileInfo(path);
+ if (file.Exists)
+ {
+ return new BclStorageFile(file);
+ }
}
return null;
diff --git a/src/Avalonia.Base/Platform/Storage/PickerOptions.cs b/src/Avalonia.Base/Platform/Storage/PickerOptions.cs
index ed061aa2d5..07f99f32c8 100644
--- a/src/Avalonia.Base/Platform/Storage/PickerOptions.cs
+++ b/src/Avalonia.Base/Platform/Storage/PickerOptions.cs
@@ -6,7 +6,7 @@
public class PickerOptions
{
///
- /// Gets or sets the text that appears in the title bar of a folder dialog.
+ /// Gets or sets the text that appears in the title bar of a picker.
///
public string? Title { get; set; }
diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs
index d11596d6be..e3ea21d1eb 100644
--- a/src/Avalonia.Base/Point.cs
+++ b/src/Avalonia.Base/Point.cs
@@ -16,13 +16,6 @@ namespace Avalonia
#endif
readonly struct Point : IEquatable
{
- static Point()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(Point).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
///
/// The X position.
///
@@ -171,6 +164,19 @@ namespace Avalonia
/// The resulting point.
public static Point operator *(Point point, Matrix matrix) => matrix.Transform(point);
+ ///
+ /// Computes the Euclidean distance between the two given points.
+ ///
+ /// The first point.
+ /// The second point.
+ /// The Euclidean distance.
+ public static double Distance(Point value1, Point value2)
+ {
+ double distanceSquared = ((value2.X - value1.X) * (value2.X - value1.X)) +
+ ((value2.Y - value1.Y) * (value2.Y - value1.Y));
+ return Math.Sqrt(distanceSquared);
+ }
+
///
/// Parses a string.
///
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
index 4518289335..a69d7b7094 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
@@ -216,7 +216,7 @@ namespace Avalonia.PropertyStore
valueChanged = !EqualityComparer.Default.Equals(Value, v);
Value = v;
Priority = priority;
- if (_uncommon is not null)
+ if (!isCoercedDefaultValue && _uncommon is not null)
_uncommon._uncoercedValue = value;
}
@@ -225,7 +225,7 @@ namespace Avalonia.PropertyStore
baseValueChanged = !EqualityComparer.Default.Equals(_baseValue, v);
_baseValue = v;
BasePriority = priority;
- if (_uncommon is not null)
+ if (!isCoercedDefaultValue && _uncommon is not null)
_uncommon._uncoercedBaseValue = value;
}
diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs
index d9218ab36e..433f46b66f 100644
--- a/src/Avalonia.Base/Rect.cs
+++ b/src/Avalonia.Base/Rect.cs
@@ -11,11 +11,6 @@ namespace Avalonia
///
public readonly struct Rect : IEquatable
{
- static Rect()
- {
- Animation.Animation.RegisterAnimator(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
- }
-
///
/// The X position.
///
diff --git a/src/Avalonia.Base/RelativePoint.cs b/src/Avalonia.Base/RelativePoint.cs
index 71c6a5cc15..5f04f4d57f 100644
--- a/src/Avalonia.Base/RelativePoint.cs
+++ b/src/Avalonia.Base/RelativePoint.cs
@@ -54,13 +54,6 @@ namespace Avalonia
private readonly RelativeUnit _unit;
- static RelativePoint()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
///
/// Initializes a new instance of the struct.
///
diff --git a/src/Avalonia.Base/RenderTargetNotReadyException.cs b/src/Avalonia.Base/RenderTargetNotReadyException.cs
new file mode 100644
index 0000000000..e34dd41709
--- /dev/null
+++ b/src/Avalonia.Base/RenderTargetNotReadyException.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Avalonia;
+
+public class RenderTargetNotReadyException : Exception
+{
+ public RenderTargetNotReadyException()
+ {
+ }
+
+ public RenderTargetNotReadyException(string message)
+ : base(message)
+ {
+ }
+
+ public RenderTargetNotReadyException(Exception innerException)
+ : base(null, innerException)
+ {
+ }
+
+ public RenderTargetNotReadyException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+}
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
index 80b6bb9d2a..64aa1736f8 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations;
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
index 455e9ebb5f..4dbeb7f152 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
@@ -5,8 +5,6 @@ using System.Numerics;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
index 1500e88abe..89d5a73612 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
@@ -2,9 +2,6 @@ using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Transport;
-
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
public class CompositionAnimationGroup : CompositionObject, ICompositionAnimationBase
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
index ec2972044e..80263884bb 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
@@ -3,8 +3,6 @@ using System;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
index 6c7c556456..569a98e93e 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
namespace Avalonia.Rendering.Composition.Animations
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs
index b14d2c0b19..43bc642c73 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs
@@ -2,8 +2,6 @@ using System;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
internal interface IAnimationInstance : IServerClockItem
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs
index 91b37a81af..0fd976684e 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs
@@ -2,8 +2,6 @@
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
index d9adf261f8..ca8566f804 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
@@ -4,8 +4,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Rendering.Composition.Transport;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
index e19c0ff098..33caef5e68 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
@@ -1,8 +1,6 @@
using System;
using System.Numerics;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
@@ -19,6 +17,12 @@ namespace Avalonia.Rendering.Composition.Animations
public static ScalarInterpolator Instance { get; } = new ScalarInterpolator();
}
+ class DoubleInterpolator : IInterpolator
+ {
+ public double Interpolate(double @from, double to, float progress) => @from + (to - @from) * progress;
+
+ public static DoubleInterpolator Instance { get; } = new ();
+ }
class Vector2Interpolator : IInterpolator
{
@@ -28,6 +32,15 @@ namespace Avalonia.Rendering.Composition.Animations
public static Vector2Interpolator Instance { get; } = new Vector2Interpolator();
}
+ class VectorInterpolator : IInterpolator
+ {
+ public Vector Interpolate(Vector @from, Vector to, float progress)
+ => new(DoubleInterpolator.Instance.Interpolate(from.X, to.X, progress),
+ DoubleInterpolator.Instance.Interpolate(from.Y, to.Y, progress));
+
+ public static VectorInterpolator Instance { get; } = new ();
+ }
+
class Vector3Interpolator : IInterpolator
{
public Vector3 Interpolate(Vector3 @from, Vector3 to, float progress)
@@ -36,6 +49,16 @@ namespace Avalonia.Rendering.Composition.Animations
public static Vector3Interpolator Instance { get; } = new Vector3Interpolator();
}
+ class Vector3DInterpolator : IInterpolator
+ {
+ public Vector3D Interpolate(Vector3D @from, Vector3D to, float progress)
+ => new(DoubleInterpolator.Instance.Interpolate(from.X, to.X, progress),
+ DoubleInterpolator.Instance.Interpolate(from.Y, to.Y, progress),
+ DoubleInterpolator.Instance.Interpolate(from.Z, to.Z, progress));
+
+ public static Vector3DInterpolator Instance { get; } = new ();
+ }
+
class Vector4Interpolator : IInterpolator
{
public Vector4 Interpolate(Vector4 @from, Vector4 to, float progress)
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
index d21a4d06e3..6cfc279580 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
@@ -2,8 +2,6 @@ using System;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
index 986110b5e6..f29bb71ede 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
@@ -4,8 +4,6 @@ using Avalonia.Animation;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
index 79045ccff8..7c4109785d 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using Avalonia.Animation.Easings;
using Avalonia.Rendering.Composition.Expressions;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs
index af3c344999..3fb6fd6796 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs
@@ -1,8 +1,6 @@
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Animations
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index 61a3f7e831..4db14ba183 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -10,8 +10,6 @@ using Avalonia.Media;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.VisualTree;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition;
///
@@ -144,93 +142,6 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
QueueUpdate();
}
- private static void SyncChildren(Visual v)
- {
- //TODO: Optimize by moving that logic to Visual itself
- if(v.CompositionVisual == null)
- return;
- var compositionChildren = v.CompositionVisual.Children;
- var visualChildren = (AvaloniaList)v.GetVisualChildren();
-
- PooledList<(Visual visual, int index)>? sortedChildren = null;
- if (v.HasNonUniformZIndexChildren && visualChildren.Count > 1)
- {
- sortedChildren = new (visualChildren.Count);
- for (var c = 0; c < visualChildren.Count; c++)
- sortedChildren.Add((visualChildren[c], c));
-
- // Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements.
- sortedChildren.Sort(static (lhs, rhs) =>
- {
- var result = lhs.visual.ZIndex.CompareTo(rhs.visual.ZIndex);
- return result == 0 ? lhs.index.CompareTo(rhs.index) : result;
- });
- }
-
- var childVisual = v.ChildCompositionVisual;
-
- // Check if the current visual somehow got migrated to another compositor
- if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor)
- childVisual = null;
-
- var expectedCount = visualChildren.Count;
- if (childVisual != null)
- expectedCount++;
-
- if (compositionChildren.Count == expectedCount)
- {
- bool mismatch = false;
- if (sortedChildren != null)
- for (var c = 0; c < visualChildren.Count; c++)
- {
- if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual))
- {
- mismatch = true;
- break;
- }
- }
- else
- for (var c = 0; c < visualChildren.Count; c++)
- if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual))
- {
- mismatch = true;
- break;
- }
-
- if (childVisual != null &&
- !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual))
- mismatch = true;
-
- if (!mismatch)
- {
- sortedChildren?.Dispose();
- return;
- }
- }
-
- compositionChildren.Clear();
- if (sortedChildren != null)
- {
- foreach (var ch in sortedChildren)
- {
- var compositionChild = ch.visual.CompositionVisual;
- if (compositionChild != null)
- compositionChildren.Add(compositionChild);
- }
- sortedChildren.Dispose();
- }
- else
- foreach (var ch in visualChildren)
- {
- var compositionChild = ch.CompositionVisual;
- if (compositionChild != null)
- compositionChildren.Add(compositionChild);
- }
-
- if (childVisual != null)
- compositionChildren.Add(childVisual);
- }
-
private void UpdateCore()
{
_queuedUpdate = false;
@@ -240,36 +151,7 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
if(comp == null)
continue;
- // TODO: Optimize all of that by moving to the Visual itself, so we won't have to recalculate every time
- comp.Offset = new Vector3((float)visual.Bounds.Left, (float)visual.Bounds.Top, 0);
- comp.Size = new Vector2((float)visual.Bounds.Width, (float)visual.Bounds.Height);
- comp.Visible = visual.IsVisible;
- comp.Opacity = (float)visual.Opacity;
- comp.ClipToBounds = visual.ClipToBounds;
- comp.Clip = visual.Clip?.PlatformImpl;
-
-
- if (!Equals(comp.OpacityMask, visual.OpacityMask))
- comp.OpacityMask = visual.OpacityMask?.ToImmutable();
-
- if (!comp.Effect.EffectEquals(visual.Effect))
- comp.Effect = visual.Effect?.ToImmutable();
-
- comp.RenderOptions = visual.RenderOptions;
-
- var renderTransform = Matrix.Identity;
-
- if (visual.HasMirrorTransform)
- renderTransform = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
-
- if (visual.RenderTransform != null)
- {
- var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
- var offset = Matrix.CreateTranslation(origin);
- renderTransform *= (-offset) * visual.RenderTransform.Value * (offset);
- }
-
- comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform);
+ visual.SynchronizeCompositionProperties();
try
{
@@ -281,11 +163,11 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
_recorder.Reset();
}
- SyncChildren(visual);
+ visual.SynchronizeCompositionChildVisuals();
}
foreach(var v in _recalculateChildren)
if (!_dirty.Contains(v))
- SyncChildren(v);
+ v.SynchronizeCompositionChildVisuals();
_dirty.Clear();
_recalculateChildren.Clear();
CompositionTarget.Size = _root.ClientSize;
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
index b7ed6fe612..598d4163d1 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
@@ -28,7 +28,7 @@ public abstract class CompositionCustomVisualHandler
_host.Compositor.VerifyAccess();
}
- protected Vector2 EffectiveSize
+ protected Vector EffectiveSize
{
get
{
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
index 4ce8a9f9ae..c4e2038450 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
@@ -2,11 +2,8 @@ using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition;
-
///
/// A composition visual that holds a list of drawing commands issued by
///
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
index ce728f86a2..31cbaf2a29 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition;
+
+[NotClientImplementable]
public interface ICompositionGpuInterop
{
///
@@ -84,14 +87,22 @@ public enum CompositionGpuImportedImageSynchronizationCapabilities
///
/// An imported GPU object that's usable by composition APIs
///
+[NotClientImplementable]
public interface ICompositionGpuImportedObject : IAsyncDisposable
{
///
/// Tracks the import status of the object. Once the task is completed,
/// the user code is allowed to free the resource owner in case when a non-owning
- /// sharing handle was used
+ /// sharing handle was used.
///
+ Task ImportCompleted { get; }
+
+ ///
+ /// ImportCompleted (recommended replacement)
+ [Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
Task ImportCompeted { get; }
+
///
/// Indicates if the device context this instance is associated with is no longer available
///
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
index 1643ec6e8d..12a252ed96 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
@@ -67,18 +67,20 @@ abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject
Context = context;
Feature = feature;
- ImportCompeted = Compositor.InvokeServerJobAsync(Import);
+ ImportCompleted = Compositor.InvokeServerJobAsync(Import);
}
protected abstract void Import();
public abstract void Dispose();
- public Task ImportCompeted { get; }
+ public Task ImportCompleted { get; }
+
+ public Task ImportCompeted => ImportCompleted;
public bool IsLost => Context.IsLost;
public ValueTask DisposeAsync() => new(Compositor.InvokeServerJobAsync(() =>
{
- if (ImportCompeted.Status == TaskStatus.RanToCompletion)
+ if (ImportCompleted.Status == TaskStatus.RanToCompletion)
Dispose();
}));
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
index 942b27d84e..9392c6a617 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
@@ -7,8 +7,6 @@ using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
index 8052d9c32c..66f52c5f80 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
@@ -6,8 +6,6 @@ using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
index 91d2fb59de..8ffdd649d5 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
@@ -4,8 +4,6 @@ using System.Numerics;
using Avalonia.Collections.Pooled;
using Avalonia.VisualTree;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
@@ -70,7 +68,7 @@ namespace Avalonia.Rendering.Composition
return false;
}
- var m33 = MatrixUtils.ToMatrix(m.Value);
+ var m33 = m.Value;
return m33.TryInvert(out matrix);
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
index dde9dcd6fb..902a195098 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
@@ -13,9 +13,6 @@ using Avalonia.Rendering.Composition.Transport;
using Avalonia.Threading;
using Avalonia.Utilities;
-
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
@@ -130,7 +127,7 @@ namespace Avalonia.Rendering.Composition
Dispatcher.UIThread.VerifyAccess();
using var noPump = NonPumpingLockHelper.Use();
- _nextCommit ??= new();
+ var commit = _nextCommit ??= new();
(_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead);
while (_invokeBeforeCommitRead.Count > 0)
@@ -188,7 +185,7 @@ namespace Avalonia.Rendering.Composition
}, TaskContinuationOptions.ExecuteSynchronously);
_nextCommit = null;
- return _pendingBatch;
+ return commit;
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
index 9b9c44b95f..f79828c9b4 100644
--- a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
@@ -1,7 +1,5 @@
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
index cb5e21be00..97083ccaf5 100644
--- a/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
@@ -42,22 +42,31 @@ class ServerCompositionRenderData : SimpleServerRenderResource
_items.Add(reader.ReadObject());
var collector = s_resourceHashSetPool.Get();
- foreach(var item in _items)
- if (item is IRenderDataItemWithServerResources resourceItem)
- resourceItem.Collect(collector);
-
+ CollectResources(_items, collector);
+
foreach (var r in collector.Resources)
{
_referencedResources.Add(r);
r.AddObserver(this);
}
-
+
collector.Resources.Clear();
s_resourceHashSetPool.ReturnAndSetNull(ref collector);
base.DeserializeChangesCore(reader, committedAt);
}
+ private static void CollectResources(PooledInlineList items, IRenderDataServerResourcesCollector collector)
+ {
+ foreach (var item in items)
+ {
+ if (item is IRenderDataItemWithServerResources resourceItem)
+ resourceItem.Collect(collector);
+ else if (item is RenderDataPushNode pushNode)
+ CollectResources(pushNode.Children, collector);
+ }
+ }
+
public Rect? Bounds
{
get
@@ -133,4 +142,4 @@ class ServerCompositionRenderData : SimpleServerRenderResource
Reset();
base.Dispose();
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
index b01321edd8..6d9b9e23a8 100644
--- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
+++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
@@ -1,5 +1,3 @@
-// Special license applies License.md
-
using System;
using Avalonia.VisualTree;
diff --git a/src/Avalonia.Base/Rendering/Composition/Enums.cs b/src/Avalonia.Base/Rendering/Composition/Enums.cs
index cc37b25701..20b3c31206 100644
--- a/src/Avalonia.Base/Rendering/Composition/Enums.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Enums.cs
@@ -1,7 +1,5 @@
using System;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
public enum CompositionBlendMode
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
index 896ae4fc3a..4164f96c30 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
@@ -4,8 +4,6 @@ using System.Numerics;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
///
@@ -16,6 +14,7 @@ namespace Avalonia.Rendering.Composition.Expressions
private readonly DelegateExpressionFfi _registry;
static float Lerp(float a, float b, float p) => p * (b - a) + a;
+ static double Lerp(double a, double b, double p) => p * (b - a) + a;
static Matrix3x2 Inverse(Matrix3x2 m)
{
@@ -34,6 +33,13 @@ namespace Avalonia.Rendering.Composition.Expressions
var t = MathUtilities.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
return t * t * (3.0f - 2.0f * t);
}
+
+ static double SmoothStep(double edge0, double edge1, double x)
+ {
+ var t = MathUtilities.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
+ return t * t * (3.0f - 2.0f * t);
+ }
+
static Vector2 SmoothStep(Vector2 edge0, Vector2 edge1, Vector2 x)
{
@@ -43,6 +49,15 @@ namespace Avalonia.Rendering.Composition.Expressions
);
}
+
+ static Vector SmoothStep(Vector edge0, Vector edge1, Vector x)
+ {
+ return new (
+ SmoothStep(edge0.X, edge1.X, x.X),
+ SmoothStep(edge0.Y, edge1.Y, x.Y)
+ );
+ }
+
static Vector3 SmoothStep(Vector3 edge0, Vector3 edge1, Vector3 x)
{
return new Vector3(
@@ -52,6 +67,15 @@ namespace Avalonia.Rendering.Composition.Expressions
);
}
+
+ static Vector3D SmoothStep(Vector3D edge0, Vector3D edge1, Vector3D x)
+ {
+ return new (
+ SmoothStep(edge0.X, edge1.X, x.X),
+ SmoothStep(edge0.Y, edge1.Y, x.Y),
+ SmoothStep(edge0.Z, edge1.Z, x.Z)
+ );
+ }
static Vector4 SmoothStep(Vector4 edge0, Vector4 edge1, Vector4 x)
{
@@ -69,7 +93,9 @@ namespace Avalonia.Rendering.Composition.Expressions
{
{"Abs", (float f) => Math.Abs(f)},
{"Abs", (Vector2 v) => Vector2.Abs(v)},
+ {"Abs", (Vector v) => v.Abs()},
{"Abs", (Vector3 v) => Vector3.Abs(v)},
+ {"Abs", (Vector3D v) => v.Abs()},
{"Abs", (Vector4 v) => Vector4.Abs(v)},
{"ACos", (float f) => (float) Math.Acos(f)},
@@ -79,7 +105,9 @@ namespace Avalonia.Rendering.Composition.Expressions
{"Clamp", (float a1, float a2, float a3) => MathUtilities.Clamp(a1, a2, a3)},
{"Clamp", (Vector2 a1, Vector2 a2, Vector2 a3) => Vector2.Clamp(a1, a2, a3)},
+ {"Clamp", (Vector a1, Vector a2, Vector a3) => Vector.Clamp(a1, a2, a3)},
{"Clamp", (Vector3 a1, Vector3 a2, Vector3 a3) => Vector3.Clamp(a1, a2, a3)},
+ {"Clamp", (Vector3D a1, Vector3D a2, Vector3D a3) => Vector3D.Clamp(a1, a2, a3)},
{"Clamp", (Vector4 a1, Vector4 a2, Vector4 a3) => Vector4.Clamp(a1, a2, a3)},
{"Concatenate", (Quaternion a1, Quaternion a2) => Quaternion.Concatenate(a1, a2)},
@@ -109,11 +137,15 @@ namespace Avalonia.Rendering.Composition.Expressions
},
{"Distance", (Vector2 a1, Vector2 a2) => Vector2.Distance(a1, a2)},
+ {"Distance", (Vector a1, Vector a2) => Vector.Distance(a1, a2)},
{"Distance", (Vector3 a1, Vector3 a2) => Vector3.Distance(a1, a2)},
+ {"Distance", (Vector3D a1, Vector3D a2) => Vector3D.Distance(a1, a2)},
{"Distance", (Vector4 a1, Vector4 a2) => Vector4.Distance(a1, a2)},
{"DistanceSquared", (Vector2 a1, Vector2 a2) => Vector2.DistanceSquared(a1, a2)},
+ {"DistanceSquared", (Vector a1, Vector a2) => Vector.DistanceSquared(a1, a2)},
{"DistanceSquared", (Vector3 a1, Vector3 a2) => Vector3.DistanceSquared(a1, a2)},
+ {"DistanceSquared", (Vector3D a1, Vector3D a2) => Vector3D.DistanceSquared(a1, a2)},
{"DistanceSquared", (Vector4 a1, Vector4 a2) => Vector4.DistanceSquared(a1, a2)},
{"Floor", (float v) => (float) Math.Floor(v)},
@@ -123,18 +155,24 @@ namespace Avalonia.Rendering.Composition.Expressions
{"Length", (Vector2 a1) => a1.Length()},
+ {"Length", (Vector a1) => a1.Length},
{"Length", (Vector3 a1) => a1.Length()},
+ {"Length", (Vector3D a1) => a1.Length},
{"Length", (Vector4 a1) => a1.Length()},
{"Length", (Quaternion a1) => a1.Length()},
{"LengthSquared", (Vector2 a1) => a1.LengthSquared()},
+ {"LengthSquared", (Vector a1) => a1*a1},
{"LengthSquared", (Vector3 a1) => a1.LengthSquared()},
+ {"LengthSquared", (Vector3D a1) => Vector3D.Dot(a1, a1)},
{"LengthSquared", (Vector4 a1) => a1.LengthSquared()},
{"LengthSquared", (Quaternion a1) => a1.LengthSquared()},
{"Lerp", (float a1, float a2, float a3) => Lerp(a1, a2, a3)},
{"Lerp", (Vector2 a1, Vector2 a2, float a3) => Vector2.Lerp(a1, a2, a3)},
+ {"Lerp", (Vector a1, Vector a2, float a3) => new Vector(Lerp(a1.X, a2.X, a3), Lerp(a1.Y, a2.Y, a3))},
{"Lerp", (Vector3 a1, Vector3 a2, float a3) => Vector3.Lerp(a1, a2, a3)},
+ {"Lerp", (Vector3D a1, Vector3D a2, float a3) => new Vector3D(Lerp(a1.X, a2.X, a3), Lerp(a1.Y, a2.Y, a3), Lerp(a1.Z, a2.Z, a3))},
{"Lerp", (Vector4 a1, Vector4 a2, float a3) => Vector4.Lerp(a1, a2, a3)},
@@ -173,24 +211,31 @@ namespace Avalonia.Rendering.Composition.Expressions
{"Max", (float a1, float a2) => Math.Max(a1, a2)},
{"Max", (Vector2 a1, Vector2 a2) => Vector2.Max(a1, a2)},
+ {"Max", (Vector a1, Vector a2) => Vector.Max(a1, a2)},
{"Max", (Vector3 a1, Vector3 a2) => Vector3.Max(a1, a2)},
+ {"Max", (Vector3D a1, Vector3D a2) => Vector3D.Max(a1, a2)},
{"Max", (Vector4 a1, Vector4 a2) => Vector4.Max(a1, a2)},
{"Min", (float a1, float a2) => Math.Min(a1, a2)},
{"Min", (Vector2 a1, Vector2 a2) => Vector2.Min(a1, a2)},
+ {"Min", (Vector a1, Vector a2) => Vector.Min(a1, a2)},
{"Min", (Vector3 a1, Vector3 a2) => Vector3.Min(a1, a2)},
+ {"Min", (Vector3D a1, Vector3D a2) => Vector3D.Min(a1, a2)},
{"Min", (Vector4 a1, Vector4 a2) => Vector4.Min(a1, a2)},
{"Mod", (float a, float b) => a % b},
{"Normalize", (Quaternion a) => Quaternion.Normalize(a)},
{"Normalize", (Vector2 a) => Vector2.Normalize(a)},
+ {"Normalize", (Vector a) => Vector.Normalize(a)},
{"Normalize", (Vector3 a) => Vector3.Normalize(a)},
+ {"Normalize", (Vector3D a) => Vector3D.Normalize(a)},
{"Normalize", (Vector4 a) => Vector4.Normalize(a)},
{"Pow", (float a, float b) => (float) Math.Pow(a, b)},
{"Quaternion.CreateFromAxisAngle", (Vector3 a, float b) => Quaternion.CreateFromAxisAngle(a, b)},
+ {"Quaternion.CreateFromAxisAngle", (Vector3D a, float b) => Quaternion.CreateFromAxisAngle(a.ToVector3(), b)},
{"Quaternion", (float a, float b, float c, float d) => new Quaternion(a, b, c, d)},
{"Round", (float a) => (float) Math.Round(a)},
@@ -198,14 +243,18 @@ namespace Avalonia.Rendering.Composition.Expressions
{"Scale", (Matrix3x2 a, float b) => a * b},
{"Scale", (Matrix4x4 a, float b) => a * b},
{"Scale", (Vector2 a, float b) => a * b},
+ {"Scale", (Vector a, float b) => a * b},
{"Scale", (Vector3 a, float b) => a * b},
+ {"Scale", (Vector3D a, float b) => Vector3D.Multiply(a, b)},
{"Scale", (Vector4 a, float b) => a * b},
{"Sin", (float a) => (float) Math.Sin(a)},
{"SmoothStep", (float a1, float a2, float a3) => SmoothStep(a1, a2, a3)},
{"SmoothStep", (Vector2 a1, Vector2 a2, Vector2 a3) => SmoothStep(a1, a2, a3)},
+ {"SmoothStep", (Vector a1, Vector a2, Vector a3) => SmoothStep(a1, a2, a3)},
{"SmoothStep", (Vector3 a1, Vector3 a2, Vector3 a3) => SmoothStep(a1, a2, a3)},
+ {"SmoothStep", (Vector3D a1, Vector3D a2, Vector3D a3) => SmoothStep(a1, a2, a3)},
{"SmoothStep", (Vector4 a1, Vector4 a2, Vector4 a3) => SmoothStep(a1, a2, a3)},
// I have no idea how to do a spherical interpolation for a scalar value, so we are doing a linear one
@@ -222,9 +271,13 @@ namespace Avalonia.Rendering.Composition.Expressions
{"Transform", (Vector2 a, Matrix3x2 b) => Vector2.Transform(a, b)},
{"Transform", (Vector3 a, Matrix4x4 b) => Vector3.Transform(a, b)},
- {"Vector2", (float a, float b) => new Vector2(a, b)},
- {"Vector3", (float a, float b, float c) => new Vector3(a, b, c)},
+ {"Vector2", (float a, float b) => new Vector(a, b)},
+ {"Vector2", (double a, double b) => new Vector(a, b)},
+ {"Vector3", (float a, float b, float c) => new Vector3D(a, b, c)},
+ {"Vector3", (double a, double b, double c) => new Vector3D(a, b, c)},
{"Vector3", (Vector2 v2, float z) => new Vector3(v2, z)},
+ {"Vector3", (Vector v2, float z) => new Vector3D(v2.X, v2.Y, z)},
+ {"Vector3", (Vector v2, double z) => new Vector3D(v2.X, v2.Y, z)},
{"Vector4", (float a, float b, float c, float d) => new Vector4(a, b, c, d)},
{"Vector4", (Vector2 v2, float z, float w) => new Vector4(v2, z, w)},
{"Vector4", (Vector3 v3, float w) => new Vector4(v3, w)},
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
index c15487065c..c70056b5aa 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
@@ -5,8 +5,6 @@ using System.Linq;
using System.Numerics;
using Avalonia.Media;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
///
@@ -49,6 +47,46 @@ namespace Avalonia.Rendering.Composition.Expressions
}
}
+ return CallWithCast(countGroup, arguments, out result, false);
+ }
+
+ bool CallWithCast(List countGroup, IReadOnlyList arguments, out ExpressionVariant result, bool anyCast)
+ {
+ result = default;
+ foreach (var record in countGroup)
+ {
+ var match = true;
+ for (var c = 0; c < arguments.Count; c++)
+ {
+ var parameter = record.Types[c];
+ var arg = arguments[c].Type;
+ if (parameter != arg)
+ {
+ var canCast = (parameter == VariantType.Double && arg == VariantType.Scalar)
+ || (parameter == VariantType.Vector3D && arg == VariantType.Vector3)
+ || (parameter == VariantType.Vector && arg == VariantType.Vector2)
+ || (anyCast && (
+ (arg == VariantType.Double && parameter == VariantType.Scalar)
+ || (arg == VariantType.Vector3D && parameter == VariantType.Vector3)
+ || (arg == VariantType.Vector && parameter == VariantType.Vector2)
+ ));
+ if (!canCast)
+ {
+ match = false;
+ break;
+ }
+ }
+ }
+
+ if (match)
+ {
+ result = record.Delegate(arguments);
+ return true;
+ }
+ }
+
+ if (anyCast == false)
+ return CallWithCast(countGroup, arguments, out result, true);
return false;
}
@@ -75,8 +113,11 @@ namespace Avalonia.Rendering.Composition.Expressions
{
[typeof(bool)] = VariantType.Boolean,
[typeof(float)] = VariantType.Scalar,
+ [typeof(double)] = VariantType.Double,
[typeof(Vector2)] = VariantType.Vector2,
+ [typeof(Vector)] = VariantType.Vector,
[typeof(Vector3)] = VariantType.Vector3,
+ [typeof(Vector3D)] = VariantType.Vector3D,
[typeof(Vector4)] = VariantType.Vector4,
[typeof(Matrix3x2)] = VariantType.Matrix3x2,
[typeof(Matrix4x4)] = VariantType.Matrix4x4,
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
index b15da5d05d..11a0b9e227 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
@@ -4,8 +4,6 @@ using System.Globalization;
using System.Reflection;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
index f268364b54..6d52dd7de0 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
@@ -1,7 +1,5 @@
using System.Collections.Generic;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
internal struct ExpressionEvaluationContext
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs
index c32a7832a6..d8b43b46e2 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs
@@ -1,7 +1,5 @@
using System;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
internal class ExpressionParseException : Exception
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs
index 3937d48187..ce3d89a670 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs
@@ -5,8 +5,6 @@ using System.Linq;
// ReSharper disable StringLiteralTypo
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
internal class ExpressionParser
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs
index e386e058df..727a555ea9 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs
@@ -2,8 +2,6 @@ using System.Collections;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions;
internal class ExpressionTrackedObjects : IEnumerable
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
index 21f14283b5..136ef69b55 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
@@ -4,8 +4,6 @@ using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia.Media;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
internal enum VariantType
@@ -17,6 +15,8 @@ namespace Avalonia.Rendering.Composition.Expressions
Vector2,
Vector3,
Vector4,
+ Vector,
+ Vector3D,
AvaloniaMatrix,
Matrix3x2,
Matrix4x4,
@@ -38,6 +38,8 @@ namespace Avalonia.Rendering.Composition.Expressions
[FieldOffset(4)] public Vector2 Vector2;
[FieldOffset(4)] public Vector3 Vector3;
[FieldOffset(4)] public Vector4 Vector4;
+ [FieldOffset(4)] public Vector Vector;
+ [FieldOffset(4)] public Vector3D Vector3D;
[FieldOffset(4)] public Matrix AvaloniaMatrix;
[FieldOffset(4)] public Matrix3x2 Matrix3x2;
[FieldOffset(4)] public Matrix4x4 Matrix4x4;
@@ -55,6 +57,15 @@ namespace Avalonia.Rendering.Composition.Expressions
return Vector2.Y;
return default;
}
+
+ if (Type == VariantType.Vector)
+ {
+ if (ReferenceEquals(property, "X"))
+ return Vector.X;
+ if (ReferenceEquals(property, "Y"))
+ return Vector.Y;
+ return default;
+ }
if (Type == VariantType.Vector3)
{
@@ -78,6 +89,29 @@ namespace Avalonia.Rendering.Composition.Expressions
return new Vector2(Vector3.Z, Vector3.Y);
return default;
}
+
+ if (Type == VariantType.Vector3D)
+ {
+ if (ReferenceEquals(property, "X"))
+ return Vector3D.X;
+ if (ReferenceEquals(property, "Y"))
+ return Vector3D.Y;
+ if (ReferenceEquals(property, "Z"))
+ return Vector3D.Z;
+ if(ReferenceEquals(property, "XY"))
+ return new Vector(Vector3D.X, Vector3D.Y);
+ if(ReferenceEquals(property, "YX"))
+ return new Vector(Vector3D.Y, Vector3D.X);
+ if(ReferenceEquals(property, "XZ"))
+ return new Vector(Vector3D.X, Vector3D.Z);
+ if(ReferenceEquals(property, "ZX"))
+ return new Vector(Vector3D.Z, Vector3D.X);
+ if(ReferenceEquals(property, "YZ"))
+ return new Vector(Vector3D.Y, Vector3D.Z);
+ if(ReferenceEquals(property, "ZY"))
+ return new Vector(Vector3D.Z, Vector3D.Y);
+ return default;
+ }
if (Type == VariantType.Vector4)
{
@@ -115,14 +149,20 @@ namespace Avalonia.Rendering.Composition.Expressions
return AvaloniaMatrix.M11;
if (ReferenceEquals(property, "M12"))
return AvaloniaMatrix.M12;
+ if (ReferenceEquals(property, "M13"))
+ return AvaloniaMatrix.M13;
if (ReferenceEquals(property, "M21"))
return AvaloniaMatrix.M21;
if (ReferenceEquals(property, "M22"))
return AvaloniaMatrix.M22;
+ if (ReferenceEquals(property, "M23"))
+ return AvaloniaMatrix.M23;
if (ReferenceEquals(property, "M31"))
return AvaloniaMatrix.M31;
if (ReferenceEquals(property, "M32"))
return AvaloniaMatrix.M32;
+ if (ReferenceEquals(property, "M33"))
+ return AvaloniaMatrix.M33;
return default;
}
@@ -220,7 +260,13 @@ namespace Avalonia.Rendering.Composition.Expressions
Type = VariantType.Vector2,
Vector2 = value
};
-
+
+ public static implicit operator ExpressionVariant(Vector value) =>
+ new ExpressionVariant
+ {
+ Type = VariantType.Vector,
+ Vector = value
+ };
public static implicit operator ExpressionVariant(Vector3 value) =>
new ExpressionVariant
@@ -228,6 +274,13 @@ namespace Avalonia.Rendering.Composition.Expressions
Type = VariantType.Vector3,
Vector3 = value
};
+
+ public static implicit operator ExpressionVariant(Vector3D value) =>
+ new ExpressionVariant
+ {
+ Type = VariantType.Vector3D,
+ Vector3D = value
+ };
public static implicit operator ExpressionVariant(Vector4 value) =>
@@ -285,10 +338,16 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector2)
return left.Vector2 + right.Vector2;
+
+ if (left.Type == VariantType.Vector)
+ return left.Vector + right.Vector;
if (left.Type == VariantType.Vector3)
return left.Vector3 + right.Vector3;
+ if (left.Type == VariantType.Vector3D)
+ return Avalonia.Vector3D.Add(left.Vector3D, right.Vector3D);
+
if (left.Type == VariantType.Vector4)
return left.Vector4 + right.Vector4;
@@ -317,10 +376,16 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector2)
return left.Vector2 - right.Vector2;
+
+ if (left.Type == VariantType.Vector)
+ return left.Vector - right.Vector;
if (left.Type == VariantType.Vector3)
return left.Vector3 - right.Vector3;
+ if (left.Type == VariantType.Vector3D)
+ return Vector3D.Add(left.Vector3D, -right.Vector3D);
+
if (left.Type == VariantType.Vector4)
return left.Vector4 - right.Vector4;
@@ -347,9 +412,15 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector2)
return -left.Vector2;
+
+ if (left.Type == VariantType.Vector)
+ return -left.Vector;
if (left.Type == VariantType.Vector3)
return -left.Vector3;
+
+ if (left.Type == VariantType.Vector3D)
+ return -left.Vector3D;
if (left.Type == VariantType.Vector4)
return -left.Vector4;
@@ -383,14 +454,29 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2)
return left.Vector2 * right.Vector2;
+ if (left.Type == VariantType.Vector && right.Type == VariantType.Vector)
+ return Vector.Multiply(left.Vector, right.Vector);
+
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar)
return left.Vector2 * right.Scalar;
+
+ if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar)
+ return left.Vector * right.Scalar;
+
+ if (left.Type == VariantType.Vector && right.Type == VariantType.Double)
+ return left.Vector * right.Double;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3)
return left.Vector3 * right.Vector3;
+
+ if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D)
+ return Vector3D.Multiply(left.Vector3D, right.Vector3D);
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar)
return left.Vector3 * right.Scalar;
+
+ if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar)
+ return Vector3D.Multiply(left.Vector3D, right.Scalar);
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4)
return left.Vector4 * right.Vector4;
@@ -436,15 +522,33 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2)
return left.Vector2 / right.Vector2;
+ if (left.Type == VariantType.Vector && right.Type == VariantType.Vector)
+ return Vector.Divide(left.Vector, right.Vector);
+
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar)
return left.Vector2 / right.Scalar;
+
+ if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar)
+ return left.Vector / right.Scalar;
+
+ if (left.Type == VariantType.Vector && right.Type == VariantType.Double)
+ return left.Vector / right.Scalar;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3)
return left.Vector3 / right.Vector3;
+ if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D)
+ return Vector3D.Divide(left.Vector3D, right.Vector3D);
+
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar)
return left.Vector3 / right.Scalar;
+ if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar)
+ return Avalonia.Vector3D.Divide(left.Vector3D, right.Scalar);
+
+ if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double)
+ return Avalonia.Vector3D.Divide(left.Vector3D, right.Double);
+
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4)
return left.Vector4 / right.Vector4;
@@ -471,9 +575,15 @@ namespace Avalonia.Rendering.Composition.Expressions
if (Type == VariantType.Vector2)
return Vector2 == right.Vector2;
-
+
+ if (Type == VariantType.Vector)
+ return Vector == right.Vector;
+
if (Type == VariantType.Vector3)
return Vector3 == right.Vector3;
+
+ if (Type == VariantType.Vector3D)
+ return Vector3D == right.Vector3D;
if (Type == VariantType.Vector4)
return Vector4 == right.Vector4;
@@ -571,6 +681,11 @@ namespace Avalonia.Rendering.Composition.Expressions
res = (T) (object) Scalar;
return true;
}
+ if (Type == VariantType.Double)
+ {
+ res = (T)(object)Scalar;
+ return true;
+ }
}
if (typeof(T) == typeof(double))
@@ -580,6 +695,12 @@ namespace Avalonia.Rendering.Composition.Expressions
res = (T) (object) Double;
return true;
}
+
+ if (Type == VariantType.Scalar)
+ {
+ res = (T)(object)(float)Double;
+ return true;
+ }
}
if (typeof(T) == typeof(Vector2))
@@ -589,6 +710,27 @@ namespace Avalonia.Rendering.Composition.Expressions
res = (T) (object) Vector2;
return true;
}
+
+ if (Type == VariantType.Vector)
+ {
+ res = (T) (object) Vector.ToVector2();
+ return true;
+ }
+ }
+
+ if (typeof(T) == typeof(Vector))
+ {
+ if (Type == VariantType.Vector)
+ {
+ res = (T) (object) Vector;
+ return true;
+ }
+
+ if (Type == VariantType.Vector2)
+ {
+ res = (T)(object)new Vector(Vector2);
+ return true;
+ }
}
if (typeof(T) == typeof(Vector3))
@@ -598,6 +740,26 @@ namespace Avalonia.Rendering.Composition.Expressions
res = (T) (object) Vector3;
return true;
}
+ if (Type == VariantType.Vector3D)
+ {
+ res = (T) (object) Vector3D.ToVector3();
+ return true;
+ }
+ }
+
+ if (typeof(T) == typeof(Vector3D))
+ {
+ if (Type == VariantType.Vector3D)
+ {
+ res = (T) (object) Vector3D;
+ return true;
+ }
+
+ if (Type == VariantType.Vector3)
+ {
+ res = (T)(object)new Vector3D(Vector3);
+ return true;
+ }
}
if (typeof(T) == typeof(Vector4))
@@ -668,9 +830,15 @@ namespace Avalonia.Rendering.Composition.Expressions
if (typeof(T) == typeof(Vector2))
return (Vector2) (object) v;
+
+ if (typeof(T) == typeof(Vector))
+ return (Vector) (object) v;
if (typeof(T) == typeof(Vector3))
return (Vector3) (object) v;
+
+ if (typeof(T) == typeof(Vector3D))
+ return (Vector3D) (object) v;
if (typeof(T) == typeof(Vector4))
return (Vector4) (object) v;
@@ -709,8 +877,12 @@ namespace Avalonia.Rendering.Composition.Expressions
return Double.ToString(CultureInfo.InvariantCulture);
if (Type == VariantType.Vector2)
return Vector2.ToString();
+ if (Type == VariantType.Vector)
+ return Vector.ToString();
if (Type == VariantType.Vector3)
return Vector3.ToString();
+ if (Type == VariantType.Vector3D)
+ return Vector3D.ToString();
if (Type == VariantType.Vector4)
return Vector4.ToString();
if (Type == VariantType.Quaternion)
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs
index 3991300fb5..22e1521887 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs
@@ -1,8 +1,6 @@
using System;
using System.Globalization;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Expressions
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs
index 131a51b5e6..c830ca2c49 100644
--- a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs
+++ b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs
@@ -1,5 +1,3 @@
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition;
internal interface ICompositionTargetDebugEvents
diff --git a/src/Avalonia.Base/Rendering/Composition/License.md b/src/Avalonia.Base/Rendering/Composition/License.md
deleted file mode 100644
index d1b709859e..0000000000
--- a/src/Avalonia.Base/Rendering/Composition/License.md
+++ /dev/null
@@ -1,7 +0,0 @@
-Please note: Any code in this directory is excluded from the normal MIT license.
-
-This code is owned and copyright to Avalonia OU.
-
-This code may be used free of charge by any application that consumes Avalonia binary packages as a direct or indirect dependency.
-
-Explicit permission is required for any other use outside of Avalonia applications.
\ No newline at end of file
diff --git a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
index 3d8d5bae78..4ec1326103 100644
--- a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
+++ b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
@@ -1,46 +1,49 @@
using System.Numerics;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
static class MatrixUtils
{
- public static Matrix4x4 ComputeTransform(Vector2 size, Vector2 anchorPoint, Vector3 centerPoint,
- Matrix4x4 transformMatrix, Vector3 scale, float rotationAngle, Quaternion orientation, Vector3 offset)
+ public static Matrix ComputeTransform(Vector size, Vector anchorPoint, Vector3D centerPoint,
+ Matrix transformMatrix, Vector3D scale, float rotationAngle, Quaternion orientation, Vector3D offset)
{
// The math here follows the *observed* UWP behavior since there are no docs on how it's supposed to work
-
- var anchor = size * anchorPoint;
- var mat = Matrix4x4.CreateTranslation(-anchor.X, -anchor.Y, 0);
- var center = new Vector3(centerPoint.X, centerPoint.Y, centerPoint.Z);
+ var anchor = Vector.Multiply(size, anchorPoint);
+ var mat = Matrix.CreateTranslation(-anchor.X, -anchor.Y);
+
+ var center = new Vector3D(centerPoint.X, centerPoint.Y, centerPoint.Z);
if (!transformMatrix.IsIdentity)
mat = transformMatrix * mat;
- if (scale != new Vector3(1, 1, 1))
- mat *= Matrix4x4.CreateScale(scale, center);
+ if (scale != new Vector3D(1, 1, 1))
+ mat *= ToMatrix(Matrix4x4.CreateScale(scale.ToVector3(), center.ToVector3()));
//TODO: RotationAxis support
if (rotationAngle != 0)
- mat *= Matrix4x4.CreateRotationZ(rotationAngle, center);
+ mat *= ToMatrix(Matrix4x4.CreateRotationZ(rotationAngle, center.ToVector3()));
if (orientation != Quaternion.Identity)
{
if (centerPoint != default)
{
- mat *= Matrix4x4.CreateTranslation(-center)
- * Matrix4x4.CreateFromQuaternion(orientation)
- * Matrix4x4.CreateTranslation(center);
+ mat *= ToMatrix(Matrix4x4.CreateTranslation(-center.ToVector3())
+ * Matrix4x4.CreateFromQuaternion(orientation)
+ * Matrix4x4.CreateTranslation(center.ToVector3()));
}
else
- mat *= Matrix4x4.CreateFromQuaternion(orientation);
+ mat *= ToMatrix(Matrix4x4.CreateFromQuaternion(orientation));
}
if (offset != default)
- mat *= Matrix4x4.CreateTranslation(offset);
+ {
+ if (offset.Z == 0)
+ mat *= Matrix.CreateTranslation(offset.X, offset.Y);
+ else
+ mat *= ToMatrix(Matrix4x4.CreateTranslation(offset.ToVector3()));
+ }
return mat;
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs
index 5d63c8a4b3..c6ff722778 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs
@@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Threading;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server;
internal class CompositionProperty
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
index 14dd59b7e3..61f8c8451f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
@@ -8,8 +8,6 @@ using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server;
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
index 03bd965fa8..00526eb4d1 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
@@ -4,7 +4,6 @@ using System.Globalization;
using Avalonia.Media;
using Avalonia.Platform;
-// Special license applies License.md
namespace Avalonia.Rendering.Composition.Server;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs
index d9df176e66..9f0341826a 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs
@@ -1,5 +1,3 @@
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
index b9e6833d21..f2a777b4bd 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
@@ -3,8 +3,6 @@ using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
@@ -81,10 +79,9 @@ namespace Avalonia.Rendering.Composition.Server
// If we only have translation and scale, just scale the padding
if (CombinedTransformMatrix is
{
- M12: 0, M13: 0, M14: 0,
- M21: 0, M23: 0, M24: 0,
- M31: 0, M32: 0, M34: 0,
- M43: 0, M44: 1
+ M12: 0, M13: 0,
+ M21: 0, M23: 0,
+ M31: 0, M32: 0
})
padding = new Thickness(padding.Left * CombinedTransformMatrix.M11,
padding.Top * CombinedTransformMatrix.M22,
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
index 763ec3b5f6..5da8afd9cd 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
@@ -7,14 +7,12 @@ using Avalonia.Rendering.Composition.Transport;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server;
///
/// Server-side counterpart of
///
-internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual
+internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual, IServerRenderResourceObserver
{
#if DEBUG
// This is needed for debugging purposes so we could see inspect the associated visual from debugger
@@ -37,6 +35,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
{
_renderCommands?.Dispose();
_renderCommands = reader.ReadObject();
+ _renderCommands?.AddObserver(this);
}
base.DeserializeChangesCore(reader, committedAt);
}
@@ -50,6 +49,8 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.RenderCore(canvas, currentTransformedClip);
}
+ public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated();
+
#if DEBUG
public override string ToString()
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
index 9c6c78c1ad..f58403f0bc 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
@@ -31,12 +31,12 @@ internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisp
throw new PlatformGraphicsContextLostException();
// This should never happen, but check for it anyway to avoid a deadlock
- if (!image.ImportCompeted.IsCompleted)
+ if (!image.ImportCompleted.IsCompleted)
throw new InvalidOperationException("The import operation is not completed yet");
// Rethrow the import here exception
- if (image.ImportCompeted.IsFaulted)
- image.ImportCompeted.GetAwaiter().GetResult();
+ if (image.ImportCompleted.IsFaulted)
+ image.ImportCompleted.GetAwaiter().GetResult();
}
void Update(IBitmapImpl newImage, IPlatformRenderInterfaceContext context)
@@ -49,22 +49,31 @@ internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisp
public void UpdateWithAutomaticSync(CompositionImportedGpuImage image)
{
- PerformSanityChecks(image);
- Update(image.Image.SnapshotWithAutomaticSync(), image.Context);
+ using (Compositor.RenderInterface.EnsureCurrent())
+ {
+ PerformSanityChecks(image);
+ Update(image.Image.SnapshotWithAutomaticSync(), image.Context);
+ }
}
public void UpdateWithKeyedMutex(CompositionImportedGpuImage image, uint acquireIndex, uint releaseIndex)
{
- PerformSanityChecks(image);
- Update(image.Image.SnapshotWithKeyedMutex(acquireIndex, releaseIndex), image.Context);
+ using (Compositor.RenderInterface.EnsureCurrent())
+ {
+ PerformSanityChecks(image);
+ Update(image.Image.SnapshotWithKeyedMutex(acquireIndex, releaseIndex), image.Context);
+ }
}
public void UpdateWithSemaphores(CompositionImportedGpuImage image, CompositionImportedGpuSemaphore wait, CompositionImportedGpuSemaphore signal)
{
- PerformSanityChecks(image);
- if (!wait.IsUsable || !signal.IsUsable)
- throw new PlatformGraphicsContextLostException();
- Update(image.Image.SnapshotWithSemaphores(wait.Semaphore, signal.Semaphore), image.Context);
+ using (Compositor.RenderInterface.EnsureCurrent())
+ {
+ PerformSanityChecks(image);
+ if (!wait.IsUsable || !signal.IsUsable)
+ throw new PlatformGraphicsContextLostException();
+ Update(image.Image.SnapshotWithSemaphores(wait.Semaphore, signal.Semaphore), image.Context);
+ }
}
public void Dispose()
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs
index 88f10ba642..a20eaabd7b 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs
@@ -1,5 +1,3 @@
-// Special license applies License.md
-
using System;
using Avalonia.Platform;
using Avalonia.Utilities;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
index 7672bcb14d..9dc5c27d79 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
@@ -9,8 +9,6 @@ using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
@@ -128,12 +126,25 @@ namespace Avalonia.Rendering.Composition.Server
if (_renderTarget?.IsCorrupted == true)
{
- _renderTarget!.Dispose();
+ _layer?.Dispose();
+ _layer = null;
+ _renderTarget.Dispose();
_renderTarget = null;
_redrawRequested = true;
}
- _renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
+ try
+ {
+ _renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
+ }
+ catch (RenderTargetNotReadyException)
+ {
+ return;
+ }
+ catch (RenderTargetCorruptedException)
+ {
+ return;
+ }
if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested)
return;
@@ -157,14 +168,15 @@ namespace Avalonia.Rendering.Composition.Server
_redrawRequested = false;
using (var targetContext = _renderTarget.CreateDrawingContext())
{
- var layerSize = Size * Scaling;
+ var size = Size;
+ var layerSize = size * Scaling;
if (layerSize != _layerSize || _layer == null || _layer.IsCorrupted)
{
_layer?.Dispose();
_layer = null;
- _layer = targetContext.CreateLayer(Size);
+ _layer = targetContext.CreateLayer(size);
_layerSize = layerSize;
- _dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height);
+ _dirtyRect = new Rect(0, 0, size.Width, size.Height);
}
if (_dirtyRect.Width != 0 || _dirtyRect.Height != 0)
@@ -185,7 +197,7 @@ namespace Avalonia.Rendering.Composition.Server
else
targetContext.DrawBitmap(_layer, 1,
new Rect(_layerSize),
- new Rect(Size));
+ new Rect(size));
if (DebugOverlays != RendererDebugOverlays.None)
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs
index 405c7879b7..c1037d5c67 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs
@@ -1,5 +1,3 @@
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server;
partial class ServerCompositionVisual
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index 0eeffda123..45515a37e2 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -6,8 +6,6 @@ using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
@@ -51,7 +49,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
}
var transform = GlobalTransformMatrix;
- canvas.PostTransform = MatrixUtils.ToMatrix(transform);
+ canvas.PostTransform = transform;
canvas.Transform = Matrix.Identity;
if (Effect != null)
@@ -71,7 +69,7 @@ namespace Avalonia.Rendering.Composition.Server
RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix
- canvas.PostTransform = MatrixUtils.ToMatrix(transform);
+ canvas.PostTransform = transform;
canvas.Transform = Matrix.Identity;
if (OpacityMaskBrush != null)
@@ -106,8 +104,8 @@ namespace Avalonia.Rendering.Composition.Server
return ref _readback2;
}
- public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity;
- public Matrix4x4 GlobalTransformMatrix { get; private set; }
+ public Matrix CombinedTransformMatrix { get; private set; } = Matrix.Identity;
+ public Matrix GlobalTransformMatrix { get; private set; }
public record struct UpdateResult(Rect? Bounds, bool InvalidatedOld, bool InvalidatedNew)
{
@@ -134,12 +132,12 @@ namespace Avalonia.Rendering.Composition.Server
{
CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint,
// HACK: Ignore RenderTransform set by the adorner layer
- AdornedVisual != null ? Matrix4x4.Identity : TransformMatrix,
+ AdornedVisual != null ? Matrix.Identity : TransformMatrix,
Scale, RotationAngle, Orientation, Offset);
_combinedTransformDirty = false;
}
- var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;
+ var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix.Identity;
var newTransform = CombinedTransformMatrix * parentTransform;
@@ -148,7 +146,7 @@ namespace Avalonia.Rendering.Composition.Server
if (GlobalTransformMatrix != newTransform)
{
_isBackface = Vector3.Transform(
- new Vector3(0, 0, float.PositiveInfinity), GlobalTransformMatrix).Z <= 0;
+ new Vector3(0, 0, float.PositiveInfinity), MatrixUtils.ToMatrix4x4(GlobalTransformMatrix)).Z <= 0;
positionChanged = true;
}
@@ -179,14 +177,14 @@ namespace Avalonia.Rendering.Composition.Server
TransformedOwnContentBounds = default;
else
TransformedOwnContentBounds =
- ownBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix));
+ ownBounds.TransformToAABB(GlobalTransformMatrix);
}
if (_clipSizeDirty || positionChanged)
{
_transformedClipBounds = ClipToBounds
? new Rect(new Size(Size.X, Size.Y))
- .TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix))
+ .TransformToAABB(GlobalTransformMatrix)
: null;
_clipSizeDirty = false;
@@ -249,7 +247,7 @@ namespace Avalonia.Rendering.Composition.Server
///
public struct ReadbackData
{
- public Matrix4x4 Matrix;
+ public Matrix Matrix;
public ulong Revision;
public long TargetId;
public bool Visible;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
index 38969fa582..aa1f099cf6 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
@@ -8,8 +8,6 @@ using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
index 0e52f256ee..0652e1731d 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Transport;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
index c7fdd6dea6..34f46f5e68 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
@@ -7,8 +7,6 @@ using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Server
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs
index fad1995092..105580e6ad 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs
@@ -13,8 +13,8 @@ internal interface IServerRenderResourceObserver
internal interface IServerRenderResource : IServerRenderResourceObserver
{
- void AddObserver(IServerRenderResource observer);
- void RemoveObserver(IServerRenderResource observer);
+ void AddObserver(IServerRenderResourceObserver observer);
+ void RemoveObserver(IServerRenderResourceObserver observer);
void QueuedInvalidate();
}
@@ -23,7 +23,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes
private bool _pendingInvalidation;
private bool _disposed;
public bool IsDisposed => _disposed;
- private RefCountingSmallDictionary _observers;
+ private RefCountingSmallDictionary _observers;
public SimpleServerRenderResource(ServerCompositor compositor) : base(compositor)
{
@@ -97,7 +97,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes
}
- public void AddObserver(IServerRenderResource observer)
+ public void AddObserver(IServerRenderResourceObserver observer)
{
Debug.Assert(!_disposed);
if(_disposed)
@@ -105,7 +105,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes
_observers.Add(observer);
}
- public void RemoveObserver(IServerRenderResource observer)
+ public void RemoveObserver(IServerRenderResourceObserver observer)
{
if (_disposed)
return;
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
index 992e5ba7b9..c21f1c9227 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
@@ -4,8 +4,6 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Transport
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
index 5a88f0e91d..0231c29bb3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
@@ -6,8 +6,6 @@ using System.Runtime.InteropServices;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Transport;
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
index 222e9918d2..4cb5ec25e0 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
@@ -5,8 +5,6 @@ using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Threading;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Transport;
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs
index fed0f4d4aa..edb796f832 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs
@@ -1,7 +1,5 @@
using System;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Transport;
internal class BatchStreamDebugMarkers
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs
index 603c7582a1..f308a5476d 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs
@@ -2,8 +2,6 @@ using System.Collections;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition.Transport
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs
index 6d6818256a..4f6442bd77 100644
--- a/src/Avalonia.Base/Rendering/Composition/Visual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs
@@ -3,8 +3,6 @@ using System.Numerics;
using Avalonia.Media;
using Avalonia.VisualTree;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
@@ -33,7 +31,7 @@ namespace Avalonia.Rendering.Composition
}
}
- internal Matrix4x4? TryGetServerGlobalTransform()
+ internal Matrix? TryGetServerGlobalTransform()
{
if (Root == null)
return null;
diff --git a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs
index 93e9f4d740..ebc84a2de7 100644
--- a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs
+++ b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs
@@ -1,8 +1,6 @@
using System;
using Avalonia.Rendering.Composition.Server;
-// Special license applies License.md
-
namespace Avalonia.Rendering.Composition
{
///
diff --git a/src/Avalonia.Base/Rendering/Composition/readme.md b/src/Avalonia.Base/Rendering/Composition/readme.md
deleted file mode 100644
index 6c238db191..0000000000
--- a/src/Avalonia.Base/Rendering/Composition/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Please note the composition renderer is not subject to the usual MIT license.
-
-Please contact us for more details and see [License.md](https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md)
diff --git a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
index 74c8bd107a..102cc30e87 100644
--- a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
+++ b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using Avalonia.Metadata;
using Avalonia.Platform;
@@ -14,7 +15,6 @@ namespace Avalonia.Rendering
[PrivateApi]
public class DefaultRenderTimer : IRenderTimer
{
- private IRuntimePlatform? _runtime;
private int _subscriberCount;
private Action? _tick;
private IDisposable? _subscription;
@@ -80,11 +80,9 @@ namespace Avalonia.Rendering
///
protected virtual IDisposable StartCore(Action tick)
{
- _runtime ??= AvaloniaLocator.Current.GetRequiredService();
+ var interval = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
- return _runtime.StartSystemTimer(
- TimeSpan.FromSeconds(1.0 / FramesPerSecond),
- () => tick(TimeSpan.FromMilliseconds(Environment.TickCount)));
+ return new Timer(_ => tick(TimeSpan.FromMilliseconds(Environment.TickCount)), null, interval, interval);
}
///
diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs
index 371d34b1f4..820840afbc 100644
--- a/src/Avalonia.Base/Rendering/IRenderRoot.cs
+++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs
@@ -16,9 +16,9 @@ namespace Avalonia.Rendering
///
/// Gets the renderer for the window.
///
- internal IRenderer Renderer { get; }
+ public IRenderer Renderer { get; }
- internal IHitTester HitTester { get; }
+ public IHitTester HitTester { get; }
///
/// The scaling factor to use in rendering.
diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs
index 4cb79d803d..227ff53897 100644
--- a/src/Avalonia.Base/Rendering/IRenderer.cs
+++ b/src/Avalonia.Base/Rendering/IRenderer.cs
@@ -9,7 +9,8 @@ namespace Avalonia.Rendering
///
/// Defines the interface for a renderer.
///
- internal interface IRenderer : IDisposable
+ [PrivateApi]
+ public interface IRenderer : IDisposable
{
///
/// Gets a value indicating whether the renderer should draw specific diagnostics.
@@ -73,7 +74,8 @@ namespace Avalonia.Rendering
Compositor Compositor { get; }
}
- internal interface IHitTester
+ [PrivateApi]
+ public interface IHitTester
{
///
/// Hit tests a location to find the visuals at the specified point.
diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs
index aaecf5deaf..80410ba181 100644
--- a/src/Avalonia.Base/Rendering/RenderLoop.cs
+++ b/src/Avalonia.Base/Rendering/RenderLoop.cs
@@ -42,7 +42,6 @@ namespace Avalonia.Rendering
/// Initializes a new instance of the class.
///
/// The render timer.
- /// The UI thread dispatcher.
public RenderLoop(IRenderTimer timer)
{
_timer = timer;
diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
index fcccd9c752..552ecb14ff 100644
--- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
+++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
@@ -1,11 +1,13 @@
using System;
+using Avalonia.Metadata;
namespace Avalonia.Rendering
{
///
/// Provides data for the event.
///
- internal class SceneInvalidatedEventArgs : EventArgs
+ [PrivateApi]
+ public class SceneInvalidatedEventArgs : EventArgs
{
///
/// Initializes a new instance of the class.
diff --git a/src/Avalonia.Base/Size.cs b/src/Avalonia.Base/Size.cs
index 7781aec607..5ee4541571 100644
--- a/src/Avalonia.Base/Size.cs
+++ b/src/Avalonia.Base/Size.cs
@@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct Size : IEquatable
{
- static Size()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(Size).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
///
/// A size representing infinity.
///
diff --git a/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs b/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs
index 8bdcec2e53..8fe0bb2537 100644
--- a/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs
+++ b/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs
@@ -1,6 +1,4 @@
-#nullable enable
-using System;
-using Avalonia.LogicalTree;
+using Avalonia.LogicalTree;
namespace Avalonia.Styling.Activators
{
@@ -14,7 +12,7 @@ namespace Avalonia.Styling.Activators
private readonly int _step;
private readonly int _offset;
private readonly bool _reversed;
- private int _index = -1;
+ private int? _index;
public NthChildActivator(
ILogical control,
@@ -30,7 +28,7 @@ namespace Avalonia.Styling.Activators
protected override bool EvaluateIsActive()
{
- var index = _index >= 0 ? _index : _provider.GetChildIndex(_control);
+ var index = _index ?? _provider.GetChildIndex(_control);
return NthChildSelector.Evaluate(index, _provider, _step, _offset, _reversed).IsMatch;
}
@@ -50,8 +48,7 @@ namespace Avalonia.Styling.Activators
// 1. Subscribed child index was changed
// 2. Child indexes were reset
// 3. We're a reversed (nth-last-child) selector and total count has changed
- if ((e.Child == _control || e.Action == ChildIndexChangedAction.ChildIndexesReset) ||
- (_reversed && e.Action == ChildIndexChangedAction.TotalCountChanged))
+ switch (e.Action)
{
// We're using the _index field to pass the index of the child to EvaluateIsActive
// *only* when the active state is re-evaluated via this event handler. The docs
@@ -65,16 +62,17 @@ namespace Avalonia.Styling.Activators
// IChildIndexProvider.GetChildIndex. This is because this event can be fired during
// the process of realizing an element of a virtualized list; in this case calling
// GetChildIndex may not return the correct index as the element isn't yet realized.
- _index = e.Index;
- ReevaluateIsActive();
- _index = -1;
+ case ChildIndexChangedAction.ChildIndexChanged when e.Child == _control:
+ _index = e.Index;
+ ReevaluateIsActive();
+ _index = null;
+ break;
+ case ChildIndexChangedAction.ChildIndexesReset:
+ case ChildIndexChangedAction.TotalCountChanged when _reversed:
+ _index = null;
+ ReevaluateIsActive();
+ break;
}
}
-
- private void TotalCountChanged(object? sender, EventArgs e)
- {
- if (_reversed)
- ReevaluateIsActive();
- }
}
}
diff --git a/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs b/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs
index 06848a7524..3dbf927b86 100644
--- a/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs
+++ b/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs
@@ -6,9 +6,14 @@ namespace Avalonia.Styling.Activators
internal abstract class StyleActivatorBase : IStyleActivator
{
private IStyleActivatorSink? _sink;
- private bool _value;
+ private bool? _value;
- public bool GetIsActive() => _value = EvaluateIsActive();
+ public bool GetIsActive()
+ {
+ var value = EvaluateIsActive();
+ _value ??= value;
+ return value;
+ }
public bool IsSubscribed => _sink is not null;
@@ -63,7 +68,7 @@ namespace Avalonia.Styling.Activators
///
protected bool ReevaluateIsActive()
{
- var value = EvaluateIsActive();
+ var value = GetIsActive();
if (value != _value)
{
diff --git a/src/Avalonia.Base/Thickness.cs b/src/Avalonia.Base/Thickness.cs
index 9513d04782..9673898d09 100644
--- a/src/Avalonia.Base/Thickness.cs
+++ b/src/Avalonia.Base/Thickness.cs
@@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct Thickness : IEquatable
{
- static Thickness()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
///
/// The thickness on the left.
///
diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
index e0563876bf..1efaa01442 100644
--- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
+++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
@@ -99,14 +99,15 @@ namespace Avalonia.Threading
}
}
- public static RestoreContext Ensure(DispatcherPriority priority)
+ public static RestoreContext Ensure(DispatcherPriority priority) => Ensure(Dispatcher.UIThread, priority);
+ public static RestoreContext Ensure(Dispatcher dispatcher, DispatcherPriority priority)
{
if (Current is AvaloniaSynchronizationContext avaloniaContext
&& avaloniaContext.Priority == priority)
return default;
var oldContext = Current;
- Dispatcher.UIThread.VerifyAccess();
- SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(priority));
+ dispatcher.VerifyAccess();
+ SetSynchronizationContext(dispatcher.GetContextWithPriority(priority));
return new RestoreContext(oldContext);
}
}
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
index bb1663eac0..add990bd57 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
@@ -90,7 +90,7 @@ public partial class Dispatcher
{
if (callback == null)
{
- throw new ArgumentNullException("callback");
+ throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@@ -98,7 +98,7 @@ public partial class Dispatcher
if (timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1))
{
- throw new ArgumentOutOfRangeException("timeout");
+ throw new ArgumentOutOfRangeException(nameof(timeout));
}
// Fast-Path: if on the same thread, and invoking at Send priority,
@@ -106,7 +106,7 @@ public partial class Dispatcher
// call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
- using (AvaloniaSynchronizationContext.Ensure(priority))
+ using (AvaloniaSynchronizationContext.Ensure(this, priority))
callback();
return;
}
@@ -212,7 +212,7 @@ public partial class Dispatcher
{
if (callback == null)
{
- throw new ArgumentNullException("callback");
+ throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@@ -220,7 +220,7 @@ public partial class Dispatcher
if (timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1))
{
- throw new ArgumentOutOfRangeException("timeout");
+ throw new ArgumentOutOfRangeException(nameof(timeout));
}
// Fast-Path: if on the same thread, and invoking at Send priority,
@@ -228,7 +228,7 @@ public partial class Dispatcher
// call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
- using (AvaloniaSynchronizationContext.Ensure(priority))
+ using (AvaloniaSynchronizationContext.Ensure(this, priority))
return callback();
}
@@ -304,7 +304,7 @@ public partial class Dispatcher
{
if (callback == null)
{
- throw new ArgumentNullException("callback");
+ throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@@ -379,7 +379,7 @@ public partial class Dispatcher
{
if (callback == null)
{
- throw new ArgumentNullException("callback");
+ throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@@ -581,11 +581,6 @@ public partial class Dispatcher
///
/// A Func<Task<TResult>> delegate to invoke through the dispatcher.
///
- ///
- /// The priority that determines in what order the specified
- /// callback is invoked relative to the other pending operations
- /// in the Dispatcher.
- ///
///
/// An task that completes after the task returned from callback finishes
///
diff --git a/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs b/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs
index e1833fef2b..4b60ee7479 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs
@@ -49,7 +49,7 @@ public partial class Dispatcher
try
{
_frames.Push(frame);
- using (AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Normal))
+ using (AvaloniaSynchronizationContext.Ensure(this, DispatcherPriority.Normal))
frame.Run(_controlledImpl);
}
finally
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
index 829ab4cf87..1644332aea 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
@@ -9,7 +9,9 @@ public partial class Dispatcher
private readonly DispatcherPriorityQueue _queue = new();
private bool _signaled;
private bool _explicitBackgroundProcessingRequested;
- private const int MaximumTimeProcessingBackgroundJobs = 50;
+ private const int MaximumInputStarvationTimeInFallbackMode = 50;
+ private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50;
+ private int _maximumInputStarvationTime;
void RequestBackgroundProcessing()
{
@@ -35,8 +37,8 @@ public partial class Dispatcher
lock (InstanceLock)
{
_explicitBackgroundProcessingRequested = false;
- ExecuteJobsCore();
}
+ ExecuteJobsCore(true);
}
///
@@ -130,10 +132,10 @@ public partial class Dispatcher
lock (InstanceLock)
_signaled = false;
- ExecuteJobsCore();
+ ExecuteJobsCore(false);
}
- void ExecuteJobsCore()
+ void ExecuteJobsCore(bool fromExplicitBackgroundProcessingCallback)
{
long? backgroundJobExecutionStartedAt = null;
while (true)
@@ -151,7 +153,6 @@ public partial class Dispatcher
if (job.Priority > DispatcherPriority.Input)
{
ExecuteJob(job);
- backgroundJobExecutionStartedAt = null;
}
// If platform supports pending input query, ask the platform if we can continue running low priority jobs
else if (_pendingInputImpl?.CanQueryPendingInput == true)
@@ -164,6 +165,13 @@ public partial class Dispatcher
return;
}
}
+ // We can't ask if the implementation has pending input, so we should let it to call us back
+ // Once it thinks that input is handled
+ else if (_backgroundProcessingImpl != null && !fromExplicitBackgroundProcessingCallback)
+ {
+ RequestBackgroundProcessing();
+ return;
+ }
// We can't check if there is pending input, but still need to enforce interactivity
// so we stop processing background jobs after some timeout and start a timer to continue later
else
@@ -171,7 +179,7 @@ public partial class Dispatcher
if (backgroundJobExecutionStartedAt == null)
backgroundJobExecutionStartedAt = Now;
- if (Now - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs)
+ if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime)
{
_signaled = true;
RequestBackgroundProcessing();
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
index bb252b7f55..51408daad1 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
@@ -127,7 +127,7 @@ public partial class Dispatcher
if (needToPromoteTimers)
PromoteTimers();
if (needToProcessQueue)
- ExecuteJobsCore();
+ ExecuteJobsCore(false);
UpdateOSTimer();
}
diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs
index f257072dc8..46c6699b5a 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.cs
@@ -34,6 +34,9 @@ public partial class Dispatcher : IDispatcher
_controlledImpl = _impl as IControlledDispatcherImpl;
_pendingInputImpl = _impl as IDispatcherImplWithPendingInput;
_backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing;
+ _maximumInputStarvationTime = _backgroundProcessingImpl == null ?
+ MaximumInputStarvationTimeInFallbackMode :
+ MaximumInputStarvationTimeInExplicitProcessingExplicitMode;
if (_backgroundProcessingImpl != null)
_backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing;
}
diff --git a/src/Avalonia.Base/Threading/DispatcherFrame.cs b/src/Avalonia.Base/Threading/DispatcherFrame.cs
index e826432475..1a80740420 100644
--- a/src/Avalonia.Base/Threading/DispatcherFrame.cs
+++ b/src/Avalonia.Base/Threading/DispatcherFrame.cs
@@ -38,10 +38,14 @@ public class DispatcherFrame
/// for their important criteria to be met. These frames
/// should have a timeout associated with them.
///
- public DispatcherFrame(bool exitWhenRequested)
+ public DispatcherFrame(bool exitWhenRequested) : this(Dispatcher.UIThread, exitWhenRequested)
{
- Dispatcher = Dispatcher.UIThread;
Dispatcher.VerifyAccess();
+ }
+
+ internal DispatcherFrame(Dispatcher dispatcher, bool exitWhenRequested)
+ {
+ Dispatcher = dispatcher;
_exitWhenRequested = exitWhenRequested;
_continue = true;
}
diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs
index 8bd6d3bc01..0008d771c6 100644
--- a/src/Avalonia.Base/Threading/DispatcherOperation.cs
+++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs
@@ -258,7 +258,7 @@ public class DispatcherOperation
try
{
- using (AvaloniaSynchronizationContext.Ensure(Priority))
+ using (AvaloniaSynchronizationContext.Ensure(Dispatcher, Priority))
InvokeCore();
}
finally
diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs
index 879d9d8a5f..fbdeed4edc 100644
--- a/src/Avalonia.Base/Threading/DispatcherTimer.cs
+++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs
@@ -62,7 +62,7 @@ public partial class DispatcherTimer
{
if (callback == null)
{
- throw new ArgumentNullException("callback");
+ throw new ArgumentNullException(nameof(callback));
}
Tick += callback;
@@ -112,11 +112,11 @@ public partial class DispatcherTimer
bool updateOSTimer = false;
if (value.TotalMilliseconds < 0)
- throw new ArgumentOutOfRangeException("value",
+ throw new ArgumentOutOfRangeException(nameof(value),
"TimeSpan period must be greater than or equal to zero.");
if (value.TotalMilliseconds > Int32.MaxValue)
- throw new ArgumentOutOfRangeException("value",
+ throw new ArgumentOutOfRangeException(nameof(value),
"TimeSpan period must be less than or equal to Int32.MaxValue.");
lock (_instanceLock)
@@ -253,20 +253,20 @@ public partial class DispatcherTimer
{
if (dispatcher == null)
{
- throw new ArgumentNullException("dispatcher");
+ throw new ArgumentNullException(nameof(dispatcher));
}
DispatcherPriority.Validate(priority, "priority");
if (priority == DispatcherPriority.Inactive)
{
- throw new ArgumentException("Specified priority is not valid.", "priority");
+ throw new ArgumentException("Specified priority is not valid.", nameof(priority));
}
if (interval.TotalMilliseconds < 0)
- throw new ArgumentOutOfRangeException("interval", "TimeSpan period must be greater than or equal to zero.");
+ throw new ArgumentOutOfRangeException(nameof(interval), "TimeSpan period must be greater than or equal to zero.");
if (interval.TotalMilliseconds > Int32.MaxValue)
- throw new ArgumentOutOfRangeException("interval",
+ throw new ArgumentOutOfRangeException(nameof(interval),
"TimeSpan period must be less than or equal to Int32.MaxValue.");
@@ -349,4 +349,4 @@ public partial class DispatcherTimer
// used by Dispatcher
internal long DueTimeInMs { get; private set; }
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Utilities/SmallDictionary.cs b/src/Avalonia.Base/Utilities/SmallDictionary.cs
index b6e62411da..1e63d554b4 100644
--- a/src/Avalonia.Base/Utilities/SmallDictionary.cs
+++ b/src/Avalonia.Base/Utilities/SmallDictionary.cs
@@ -51,7 +51,7 @@ internal struct InlineDictionary : IEnumerable : IEnumerable
+/// Helper method to help inlining methods that do a throw check.
+/// Equivalent of .NET6+ ArgumentNullException.ThrowIfNull() for netstandard2.0+
+///
+internal class ThrowHelper
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ThrowIfNull([NotNull] object? argument, string paramName)
+ {
+ if (argument is null)
+ {
+ ThrowArgumentNullException(paramName);
+ }
+ }
+
+ [DoesNotReturn]
+ private static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
+}
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index 3a82bf02e0..7dbb0872f5 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs
@@ -306,7 +306,7 @@ namespace Avalonia.Utilities
/// if the value could not be converted.
///
/// The value to convert.
- /// The type to convert to..
+ /// The type to convert to.
/// The culture to use.
/// A value of .
[RequiresUnreferencedCode(TrimmingMessages.TypeConversionRequiresUnreferencedCodeMessage)]
diff --git a/src/Avalonia.Base/Vector.cs b/src/Avalonia.Base/Vector.cs
index 085f043627..fffe206835 100644
--- a/src/Avalonia.Base/Vector.cs
+++ b/src/Avalonia.Base/Vector.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Numerics;
#if !BUILDTASK
using Avalonia.Animation.Animators;
#endif
@@ -17,13 +18,6 @@ namespace Avalonia
#endif
readonly struct Vector : IEquatable
{
- static Vector()
- {
-#if !BUILDTASK
- Animation.Animation.RegisterAnimator(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType));
-#endif
- }
-
///
/// The X component.
///
@@ -360,5 +354,58 @@ namespace Avalonia
x = this._x;
y = this._y;
}
+
+ internal Vector2 ToVector2() => new Vector2((float)X, (float)Y);
+
+ internal Vector(Vector2 v) : this(v.X, v.Y)
+ {
+
+ }
+
+ ///
+ /// Returns a vector whose elements are the absolute values of each of the specified vector's elements.
+ ///
+ ///
+ public Vector Abs() => new(Math.Abs(X), Math.Abs(Y));
+
+ ///
+ /// Restricts a vector between a minimum and a maximum value.
+ ///
+ public static Vector Clamp(Vector value, Vector min, Vector max) =>
+ Min(Max(value, min), max);
+
+ ///
+ /// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors
+ ///
+ public static Vector Max(Vector left, Vector right) =>
+ new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y));
+
+ ///
+ /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors
+ ///
+ public static Vector Min(Vector left, Vector right) =>
+ new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y));
+
+ ///
+ /// Computes the Euclidean distance between the two given points.
+ ///
+ /// The first point.
+ /// The second point.
+ /// The Euclidean distance.
+ public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2));
+
+ ///
+ /// Returns the Euclidean distance squared between two specified points
+ ///
+ /// The first point.
+ /// The second point.
+ /// The Euclidean distance squared.
+ public static double DistanceSquared(Vector value1, Vector value2)
+ {
+ var difference = value1 - value2;
+ return Dot(difference, difference);
+ }
+
+ public static implicit operator Vector(Vector2 v) => new(v);
}
}
diff --git a/src/Avalonia.Base/Vector3D.cs b/src/Avalonia.Base/Vector3D.cs
new file mode 100644
index 0000000000..ec6463c987
--- /dev/null
+++ b/src/Avalonia.Base/Vector3D.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Globalization;
+using System.Numerics;
+using Avalonia.Rendering.Composition.Expressions;
+using Avalonia.Utilities;
+
+namespace Avalonia;
+
+public readonly record struct Vector3D(double X, double Y, double Z)
+{
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The .
+ public static Vector3D Parse(string s)
+ {
+ using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Vector."))
+ {
+ return new Vector3D(
+ tokenizer.ReadDouble(),
+ tokenizer.ReadDouble(),
+ tokenizer.ReadDouble()
+ );
+ }
+ }
+
+ internal Vector3 ToVector3() => new Vector3((float)X, (float)Y, (float)Z);
+
+ internal Vector3D(Vector3 v) : this(v.X, v.Y, v.Z)
+ {
+
+ }
+
+ public static implicit operator Vector3D(Vector3 vector) => new(vector);
+
+ ///
+ /// Calculates the dot product of two vectors.
+ ///
+ public static double Dot(Vector3D vector1, Vector3D vector2) =>
+ (vector1.X * vector2.X)
+ + (vector1.Y * vector2.Y)
+ + (vector1.Z * vector2.Z);
+
+ ///
+ /// Adds the second to the first vector
+ ///
+ public static Vector3D Add(Vector3D left, Vector3D right) =>
+ new Vector3D(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
+
+ ///
+ /// Adds the second to the first vector
+ ///
+ public static Vector3D operator +(Vector3D left, Vector3D right) => Add(left, right);
+
+ ///
+ /// Subtracts the second from the first vector
+ ///
+ public static Vector3D Substract(Vector3D left, Vector3D right) =>
+ new Vector3D(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
+
+ ///
+ /// Subtracts the second from the first vector
+ ///
+ public static Vector3D operator -(Vector3D left, Vector3D right) => Substract(left, right);
+
+ ///
+ /// Negates the vector
+ ///
+ public static Vector3D operator -(Vector3D v) => new(-v.X, -v.Y, -v.Z);
+
+ ///
+ /// Multiplies the first vector by the second.
+ ///
+ public static Vector3D Multiply(Vector3D left, Vector3D right) =>
+ new(left.X * right.X, left.Y * right.Y, left.Z * right.Z);
+
+ ///
+ /// Multiplies the vector by the given scalar.
+ ///
+ public static Vector3D Multiply(Vector3D left, double right) =>
+ new(left.X * right, left.Y * right, left.Z * right);
+
+ ///
+ /// Multiplies the vector by the given scalar.
+ ///
+ public static Vector3D operator *(Vector3D left, double right) => Multiply(left, right);
+
+ ///
+ /// Divides the first vector by the second.
+ ///
+ public static Vector3D Divide(Vector3D left, Vector3D right) =>
+ new(left.X / right.X, left.Y / right.Y, left.Z / right.Z);
+
+ ///
+ /// Divides the vector by the given scalar.
+ ///
+ public static Vector3D Divide(Vector3D left, double right) =>
+ new(left.X / right, left.Y / right, left.Z / right);
+
+ /// Returns a vector whose elements are the absolute values of each of the specified vector's elements.
+ public Vector3D Abs() => new(Math.Abs(X), Math.Abs(Y), Math.Abs(Z));
+
+ ///
+ /// Restricts a vector between a minimum and a maximum value.
+ ///
+ public static Vector3D Clamp(Vector3D value, Vector3D min, Vector3D max) =>
+ Min(Max(value, min), max);
+
+ ///
+ /// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors
+ ///
+ public static Vector3D Max(Vector3D left, Vector3D right) =>
+ new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y), Math.Max(left.Z, right.Z));
+
+ ///
+ /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors
+ ///
+ public static Vector3D Min(Vector3D left, Vector3D right) =>
+ new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y), Math.Min(left.Z, right.Z));
+
+ ///
+ /// Length of the vector.
+ ///
+ public double Length => Math.Sqrt(Dot(this, this));
+
+ ///
+ /// Returns a normalized version of this vector.
+ ///
+ public static Vector3D Normalize(Vector3D value) => Divide(value, value.Length);
+
+ ///
+ /// Computes the squared Euclidean distance between the two given points.
+ ///
+ public static double DistanceSquared(Vector3D value1, Vector3D value2)
+ {
+ var difference = Vector3D.Substract(value1, value2);
+ return Dot(difference, difference);
+ }
+
+ ///
+ /// Computes the Euclidean distance between the two given points.
+ ///
+ public static double Distance(Vector3D value1, Vector3D value2) => Math.Sqrt(DistanceSquared(value1, value2));
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Visual.Composition.cs b/src/Avalonia.Base/Visual.Composition.cs
new file mode 100644
index 0000000000..d6a4e3e9be
--- /dev/null
+++ b/src/Avalonia.Base/Visual.Composition.cs
@@ -0,0 +1,165 @@
+using Avalonia.Collections;
+using Avalonia.Collections.Pooled;
+using Avalonia.Media;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.VisualTree;
+
+namespace Avalonia;
+
+public partial class Visual
+{
+ internal CompositionDrawListVisual? CompositionVisual { get; private set; }
+ internal CompositionVisual? ChildCompositionVisual { get; set; }
+
+
+ private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor)
+ => new CompositionDrawListVisual(compositor,
+ new ServerCompositionDrawListVisual(compositor.Server, this), this);
+
+ internal CompositionVisual AttachToCompositor(Compositor compositor)
+ {
+ if (CompositionVisual == null || CompositionVisual.Compositor != compositor)
+ {
+ CompositionVisual = CreateCompositionVisual(compositor);
+ }
+
+ return CompositionVisual;
+ }
+
+ internal virtual void DetachFromCompositor()
+ {
+ if (CompositionVisual != null)
+ {
+ if (ChildCompositionVisual != null)
+ CompositionVisual.Children.Remove(ChildCompositionVisual);
+
+ CompositionVisual.DrawList = null;
+ CompositionVisual = null;
+ }
+ }
+
+ internal virtual void SynchronizeCompositionChildVisuals()
+ {
+ if(CompositionVisual == null)
+ return;
+ var compositionChildren = CompositionVisual.Children;
+ var visualChildren = (AvaloniaList)VisualChildren;
+
+ PooledList<(Visual visual, int index)>? sortedChildren = null;
+ if (HasNonUniformZIndexChildren && visualChildren.Count > 1)
+ {
+ sortedChildren = new (visualChildren.Count);
+ for (var c = 0; c < visualChildren.Count; c++)
+ sortedChildren.Add((visualChildren[c], c));
+
+ // Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements.
+ sortedChildren.Sort(static (lhs, rhs) =>
+ {
+ var result = lhs.visual.ZIndex.CompareTo(rhs.visual.ZIndex);
+ return result == 0 ? lhs.index.CompareTo(rhs.index) : result;
+ });
+ }
+
+ var childVisual = ChildCompositionVisual;
+
+ // Check if the current visual somehow got migrated to another compositor
+ if (childVisual != null && childVisual.Compositor != CompositionVisual.Compositor)
+ childVisual = null;
+
+ var expectedCount = visualChildren.Count;
+ if (childVisual != null)
+ expectedCount++;
+
+ if (compositionChildren.Count == expectedCount)
+ {
+ bool mismatch = false;
+ if (sortedChildren != null)
+ for (var c = 0; c < visualChildren.Count; c++)
+ {
+ if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual))
+ {
+ mismatch = true;
+ break;
+ }
+ }
+ else
+ for (var c = 0; c < visualChildren.Count; c++)
+ if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual))
+ {
+ mismatch = true;
+ break;
+ }
+
+ if (childVisual != null &&
+ !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual))
+ mismatch = true;
+
+ if (!mismatch)
+ {
+ sortedChildren?.Dispose();
+ return;
+ }
+ }
+
+ compositionChildren.Clear();
+ if (sortedChildren != null)
+ {
+ foreach (var ch in sortedChildren)
+ {
+ var compositionChild = ch.visual.CompositionVisual;
+ if (compositionChild != null)
+ compositionChildren.Add(compositionChild);
+ }
+ sortedChildren.Dispose();
+ }
+ else
+ foreach (var ch in visualChildren)
+ {
+ var compositionChild = ch.CompositionVisual;
+ if (compositionChild != null)
+ compositionChildren.Add(compositionChild);
+ }
+
+ if (childVisual != null)
+ compositionChildren.Add(childVisual);
+ }
+
+ internal virtual void SynchronizeCompositionProperties()
+ {
+ if(CompositionVisual == null)
+ return;
+ var comp = CompositionVisual;
+
+ // TODO: Introduce a dirty mask like WPF has, so we don't overwrite properties every time
+
+ comp.Offset = new (Bounds.Left, Bounds.Top, 0);
+ comp.Size = new (Bounds.Width, Bounds.Height);
+ comp.Visible = IsVisible;
+ comp.Opacity = (float)Opacity;
+ comp.ClipToBounds = ClipToBounds;
+ comp.Clip = Clip?.PlatformImpl;
+
+ if (!Equals(comp.OpacityMask, OpacityMask))
+ comp.OpacityMask = OpacityMask?.ToImmutable();
+
+ if (!comp.Effect.EffectEquals(Effect))
+ comp.Effect = Effect?.ToImmutable();
+
+ comp.RenderOptions = RenderOptions;
+
+ var renderTransform = Matrix.Identity;
+
+ if (HasMirrorTransform)
+ renderTransform = new Matrix(-1.0, 0.0, 0.0, 1.0, Bounds.Width, 0);
+
+ if (RenderTransform != null)
+ {
+ var origin = RenderTransformOrigin.ToPixels(new Size(Bounds.Width, Bounds.Height));
+ var offset = Matrix.CreateTranslation(origin);
+ renderTransform *= (-offset) * RenderTransform.Value * (offset);
+ }
+
+ comp.TransformMatrix = renderTransform;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 1b7975c154..9774468e16 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -29,7 +29,7 @@ namespace Avalonia
/// extension methods defined in .
///
[UsableDuringInitialization]
- public class Visual : StyledElement
+ public partial class Visual : StyledElement
{
///
/// Defines the property.
@@ -316,9 +316,6 @@ namespace Avalonia
///
protected internal IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot);
- internal CompositionDrawListVisual? CompositionVisual { get; private set; }
- internal CompositionVisual? ChildCompositionVisual { get; set; }
-
internal RenderOptions RenderOptions { get; set; }
internal bool HasNonUniformZIndexChildren { get; private set; }
@@ -515,20 +512,6 @@ namespace Avalonia
}
}
- private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor)
- => new CompositionDrawListVisual(compositor,
- new ServerCompositionDrawListVisual(compositor.Server, this), this);
-
- internal CompositionVisual AttachToCompositor(Compositor compositor)
- {
- if (CompositionVisual == null || CompositionVisual.Compositor != compositor)
- {
- CompositionVisual = CreateCompositionVisual(compositor);
- }
-
- return CompositionVisual;
- }
-
///
/// Calls the method
/// for this control and all of its visual descendants.
@@ -547,14 +530,7 @@ namespace Avalonia
DisableTransitions();
OnDetachedFromVisualTree(e);
- if (CompositionVisual != null)
- {
- if (ChildCompositionVisual != null)
- CompositionVisual.Children.Remove(ChildCompositionVisual);
-
- CompositionVisual.DrawList = null;
- CompositionVisual = null;
- }
+ DetachFromCompositor();
DetachedFromVisualTree?.Invoke(this, e);
e.Root.Renderer.AddDirty(this);
diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
index b3cdd70fa0..e244323ed9 100644
--- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Runtime.CompilerServices;
using Avalonia.Rendering;
+using Avalonia.Utilities;
namespace Avalonia.VisualTree
{
@@ -125,9 +128,9 @@ namespace Avalonia.VisualTree
/// The visual's ancestors.
public static IEnumerable GetVisualAncestors(this Visual visual)
{
- Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
- v = v.VisualParent;
+ var v = visual.VisualParent;
while (v != null)
{
@@ -194,7 +197,7 @@ namespace Avalonia.VisualTree
/// The visual and its ancestors.
public static IEnumerable GetSelfAndVisualAncestors(this Visual visual)
{
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
yield return visual;
@@ -275,7 +278,7 @@ namespace Avalonia.VisualTree
/// The visual at the requested point.
public static Visual? GetVisualAt(this Visual visual, Point p)
{
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualAt(p, x => x.IsVisible);
}
@@ -292,7 +295,7 @@ namespace Avalonia.VisualTree
/// The visual at the requested point.
public static Visual? GetVisualAt(this Visual visual, Point p, Func filter)
{
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot();
@@ -321,7 +324,7 @@ namespace Avalonia.VisualTree
this Visual visual,
Point p)
{
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualsAt(p, x => x.IsVisible);
}
@@ -341,7 +344,7 @@ namespace Avalonia.VisualTree
Point p,
Func filter)
{
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot();
@@ -435,7 +438,7 @@ namespace Avalonia.VisualTree
///
public static IRenderRoot? GetVisualRoot(this Visual visual)
{
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
+ ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual as IRenderRoot ?? visual.VisualRoot;
}
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index 97b2686584..05aea3808e 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -21,14 +21,14 @@
-
-
-
-
+
+
+
+
-
-
+
+
@@ -57,10 +57,13 @@
+
+
+
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 4f9c7416db..bc36c610c7 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -1,12 +1,14 @@
- netstandard2.0
- exe
- false
- tools
- $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL
- true
- $(NoWarn);NU1605;CS8632
+ netstandard2.0
+ exe
+ false
+ tools
+ $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL
+ true
+ $(NoWarn);NU1605;CS8632
+ embedded
+ false
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index d4c4ae8667..80876f1819 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -304,7 +304,7 @@ namespace Avalonia.Build.Tasks
|| "Internal".Equals(classModifierText, StringComparison.OrdinalIgnoreCase))
classModifierPublic = false;
else
- throw new XamlParseException("Invalid value for x:ClassModifier. Expected value are: Public, NotPublic (internal).", precompileDirective);
+ throw new XamlParseException("Invalid value for x:ClassModifier. Expected value are: Public, NotPublic (internal).", classModifierDirective);
}
var classDirective = initialRoot.Children.OfType()
@@ -353,10 +353,16 @@ namespace Avalonia.Build.Tasks
((List)parsedXamlDocuments).Add(new XamlDocumentResource(
parsed, res.Uri, res, classType,
classModifierPublic.Value,
- populateBuilder,
- compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
- classTypeDefinition == null && classModifierPublic.Value),
- buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, classModifierPublic.Value)));
+ () => new XamlDocumentTypeBuilderProvider(
+ populateBuilder,
+ compiler.DefinePopulateMethod(
+ populateBuilder,
+ parsed,
+ populateName,
+ classTypeDefinition == null && classModifierPublic.Value),
+ buildName == null ?
+ null :
+ compiler.DefineBuildMethod(builder, parsed, buildName, classModifierPublic.Value))));
}
catch (Exception e)
{
@@ -387,10 +393,17 @@ namespace Avalonia.Build.Tasks
foreach (var document in parsedXamlDocuments)
{
+ var res = (IResource)document.FileSource!;
+
+ if (document.Usage == XamlDocumentUsage.Merged && !document.IsPublic)
+ {
+ res.Remove();
+ continue;
+ }
+
var parsed = document.XamlDocument;
- var res = (IResource)document.FileSource;
var classType = document.ClassType;
- var populateBuilder = document.TypeBuilder;
+ var populateBuilder = document.TypeBuilderProvider.TypeBuilder;
try
{
@@ -399,10 +412,9 @@ namespace Avalonia.Build.Tasks
compiler.Compile(parsed,
contextClass,
- document.PopulateMethod,
- document.BuildMethod,
- builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name,
- document.IsPublic),
+ document.TypeBuilderProvider.PopulateMethod,
+ document.TypeBuilderProvider.BuildMethod,
+ builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name, true),
(closureName, closureBaseType) =>
populateBuilder.DefineSubType(closureBaseType, closureName, false),
(closureName, returnType, parameterTypes) =>
@@ -413,7 +425,7 @@ namespace Avalonia.Build.Tasks
if (classTypeDefinition != null)
{
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
- .Methods.First(m => m.Name == document.PopulateMethod.Name);
+ .Methods.First(m => m.Name == document.TypeBuilderProvider.PopulateMethod.Name);
var designLoaderFieldType = typeSystem
.GetType("System.Action`1")
@@ -537,12 +549,12 @@ namespace Avalonia.Build.Tasks
}
if (document.IsPublic
- && (document.BuildMethod != null || classTypeDefinition != null))
+ && (document.TypeBuilderProvider.BuildMethod != null || classTypeDefinition != null))
{
- var compiledBuildMethod = document.BuildMethod == null ?
+ var compiledBuildMethod = document.TypeBuilderProvider.BuildMethod is not { } buildMethod ?
null :
typeSystem.GetTypeReference(builder).Resolve()
- .Methods.First(m => m.Name == document.BuildMethod?.Name);
+ .Methods.First(m => m.Name == buildMethod.Name);
var parameterlessConstructor = compiledBuildMethod != null ?
null :
classTypeDefinition.GetConstructors().FirstOrDefault(c =>
diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs
index aaf272c6d2..4700081e6b 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs
@@ -266,26 +266,26 @@ namespace Avalonia.Controls
MidnightBlue9 = 0xFF1C2833,
MidnightBlue10 = 0xFF17202A,
- Pomegranate = Pomegranate3,
- Alizarin = Alizarin3,
- Amethyst = Amethyst3,
- Wisteria = Wisteria3,
- BelizeHole = BelizeHole3,
- PeterRiver = PeterRiver3,
- Turquoise = Turquoise3,
- GreenSea = GreenSea3,
- Nephritis = Nephritis3,
- Emerald = Emerald3,
- Sunflower = Sunflower3,
- Orange = Orange3,
- Carrot = Carrot3,
- Pumpkin = Pumpkin3,
- Clouds = Clouds3,
- Silver = Silver3,
- Concrete = Concrete3,
- Asbestos = Asbestos3,
- WetAsphalt = WetAsphalt3,
- MidnightBlue = MidnightBlue3,
+ Pomegranate = Pomegranate6,
+ Alizarin = Alizarin6,
+ Amethyst = Amethyst6,
+ Wisteria = Wisteria6,
+ BelizeHole = BelizeHole6,
+ PeterRiver = PeterRiver6,
+ Turquoise = Turquoise6,
+ GreenSea = GreenSea6,
+ Nephritis = Nephritis6,
+ Emerald = Emerald6,
+ Sunflower = Sunflower6,
+ Orange = Orange6,
+ Carrot = Carrot6,
+ Pumpkin = Pumpkin6,
+ Clouds = Clouds6,
+ Silver = Silver6,
+ Concrete = Concrete6,
+ Asbestos = Asbestos6,
+ WetAsphalt = WetAsphalt6,
+ MidnightBlue = MidnightBlue6,
};
// See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png
diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
index 6683346eeb..deb0dfb6dd 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
@@ -45,7 +45,6 @@ namespace Avalonia.Controls.Primitives
private bool _updatingColor = false;
private bool _updatingHsvColor = false;
- private bool _coercedInitialColor = false;
private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false;
private List _hsvValues = new List();
@@ -622,7 +621,7 @@ namespace Avalonia.Controls.Primitives
// that no color has been selected by the user. Note that #00000000 is different than
// #00FFFFFF (Transparent).
//
- // In this situation, the first time the user clicks on the spectrum the third
+ // In this situation, whenever the user clicks on the spectrum, the third
// component and alpha component will remain zero. This is because the spectrum only
// controls two components at any given time.
//
@@ -633,16 +632,19 @@ namespace Avalonia.Controls.Primitives
// though the desired value is simply full color.
//
// To work around this usability issue with an initial #00000000 color, the selected
- // color is coerced (only the first time) into a color with maximum third component
- // value and maximum alpha. This can only happen once and only if those two components
- // are already zero.
+ // color is coerced into a color with maximum third component value and maximum alpha.
+ // This can only happen here in the spectrum if those two components are already zero.
+ //
+ // In the past this coercion was restricted to occur only one time. However, when
+ // ColorPicker controls are re-used or recycled #00000000 can be set multiple times.
+ // Each time needs this special logic for usability so now anytime the color is
+ // changed on the spectrum this logic will run.
//
// Also note this is NOT currently done for #00FFFFFF (Transparent) but based on
// further usability study that case may need to be handled here as well. Right now
// Transparent is treated as a normal color value with the alpha intentionally set
// to zero so the alpha slider must still be adjusted after the spectrum.
- if (!_coercedInitialColor &&
- IsLoaded)
+ if (IsLoaded)
{
bool isAlphaComponentZero = (alpha == 0.0);
bool isThirdComponentZero = false;
@@ -691,8 +693,6 @@ namespace Avalonia.Controls.Primitives
newHsv.H = 360.0;
break;
}
-
- _coercedInitialColor = true;
}
}
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
index f3100a648a..104314e54f 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
@@ -1,7 +1,8 @@
+ xmlns:primitives="using:Avalonia.Controls.Primitives"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
index fabc5d0349..520e6e8db3 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
@@ -1,6 +1,7 @@
+ xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
+ x:ClassModifier="internal">
80
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
index 789473571b..edc6246ddd 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
@@ -1,5 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml
index ebbb006fd1..c9972125a0 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml
@@ -1,6 +1,7 @@
+ xmlns:controls="using:Avalonia.Controls"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
index 8793467b36..9ca55566f3 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
@@ -4,7 +4,8 @@
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
- xmlns:globalization="using:System.Globalization">
+ xmlns:globalization="using:System.Globalization"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
index d9ba8bb9d2..c20f7935f4 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
@@ -1,7 +1,8 @@
+ xmlns:primitives="using:Avalonia.Controls.Primitives"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
index 9e123b2a1f..23f0b34ddd 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
@@ -1,6 +1,7 @@
+ xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
+ x:ClassModifier="internal">
80
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
index 472f1ca100..6b4ad68275 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
@@ -1,5 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml
index a7ad56d8b4..101a06ccb9 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml
@@ -1,6 +1,7 @@
+ xmlns:controls="using:Avalonia.Controls"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
index d4f02933f2..994a5e1bbc 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
@@ -4,7 +4,8 @@
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
- xmlns:globalization="using:System.Globalization">
+ xmlns:globalization="using:System.Globalization"
+ x:ClassModifier="internal">
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
index 30517f4b00..0026c9e074 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
@@ -807,7 +807,7 @@ namespace Avalonia.Collections
{
if (value < 0)
{
- throw new ArgumentOutOfRangeException("PageSize cannot have a negative value.");
+ throw new ArgumentOutOfRangeException(nameof(value), "PageSize cannot have a negative value.");
}
// if the Refresh is currently deferred, cache the desired PageSize
@@ -1954,7 +1954,7 @@ namespace Avalonia.Collections
// for indices larger than the count
if (index >= Count || index < 0)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
if (IsGrouping)
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index bfcd4750e3..de49d50637 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -2101,6 +2101,7 @@ namespace Avalonia.Controls
if (DataConnection.DataSource != null && !DataConnection.EventsWired)
{
DataConnection.WireEvents(DataConnection.DataSource);
+ InitializeElements(false /*recycleRows*/);
}
}
@@ -2660,25 +2661,25 @@ namespace Avalonia.Controls
internal bool ProcessDownKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessDownKeyInternal(shift, ctrl);
}
internal bool ProcessEndKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessEndKey(shift, ctrl);
}
internal bool ProcessEnterKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessEnterKey(shift, ctrl);
}
internal bool ProcessHomeKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessHomeKey(shift, ctrl);
}
@@ -2718,25 +2719,25 @@ namespace Avalonia.Controls
internal bool ProcessLeftKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessLeftKey(shift, ctrl);
}
internal bool ProcessNextKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessNextKey(shift, ctrl);
}
internal bool ProcessPriorKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessPriorKey(shift, ctrl);
}
internal bool ProcessRightKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessRightKey(shift, ctrl);
}
@@ -2854,7 +2855,7 @@ namespace Avalonia.Controls
internal bool ProcessUpKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessUpKey(shift, ctrl);
}
@@ -3124,13 +3125,13 @@ namespace Avalonia.Controls
//TODO: Ensure right button is checked for
internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
- KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
//TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
- KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
@@ -4654,7 +4655,7 @@ namespace Avalonia.Controls
private bool ProcessAKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift, out bool alt);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift, out bool alt);
if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended)
{
@@ -4923,7 +4924,7 @@ namespace Avalonia.Controls
private bool ProcessF2Key(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
if (!shift && !ctrl &&
_editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) &&
@@ -5280,7 +5281,7 @@ namespace Avalonia.Controls
private bool ProcessTabKey(KeyEventArgs e)
{
- KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessTabKey(e, shift, ctrl);
}
@@ -6099,7 +6100,7 @@ namespace Avalonia.Controls
/// Whether or not the DataGrid handled the key press.
private bool ProcessCopyKey(KeyModifiers modifiers)
{
- KeyboardHelper.GetMetaKeyState(modifiers, out bool ctrl, out bool shift, out bool alt);
+ KeyboardHelper.GetMetaKeyState(this, modifiers, out bool ctrl, out bool shift, out bool alt);
if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0)
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
index 5d080f5ef6..b2deb1c26e 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
@@ -222,7 +222,7 @@ namespace Avalonia.Controls
{
if (element == null)
{
- throw new ArgumentNullException("element");
+ throw new ArgumentNullException(nameof(element));
}
if (element is CheckBox checkBox)
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
index d1e1efdd85..a77ac985ac 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
@@ -7,7 +7,6 @@ using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Avalonia.Collections;
-using Avalonia.Utilities;
using System;
using System.ComponentModel;
using System.Linq;
@@ -24,8 +23,6 @@ namespace Avalonia.Controls
{
internal const int DATAGRIDCOLUMN_maximumWidth = 65536;
private const bool DATAGRIDCOLUMN_defaultIsReadOnly = false;
-
- private DataGridLength? _width; // Null by default, null means inherit the Width from the DataGrid
private bool? _isReadOnly;
private double? _maxWidth;
private double? _minWidth;
@@ -38,7 +35,8 @@ namespace Avalonia.Controls
private ICellEditBinding _editBinding;
private IBinding _clipboardContentBinding;
private ControlTheme _cellTheme;
- private readonly Classes _cellStyleClasses = new Classes();
+ private Classes _cellStyleClasses;
+ private bool _setWidthInternalNoCallback;
///
/// Initializes a new instance of the class.
@@ -214,6 +212,36 @@ namespace Avalonia.Controls
OwningGrid?.OnColumnVisibleStateChanged(this);
NotifyPropertyChanged(change.Property.Name);
}
+ else if (change.Property == WidthProperty)
+ {
+ if (!_settingWidthInternally)
+ {
+ InheritsWidth = false;
+ }
+ if (_setWidthInternalNoCallback == false)
+ {
+ var grid = OwningGrid;
+ var width = (change as AvaloniaPropertyChangedEventArgs).NewValue.Value;
+ if (grid != null)
+ {
+ var oldWidth = (change as AvaloniaPropertyChangedEventArgs).OldValue.Value;
+ if (width.IsStar != oldWidth.IsStar)
+ {
+ SetWidthInternalNoCallback(width);
+ IsInitialDesiredWidthDetermined = false;
+ grid.OnColumnWidthChanged(this);
+ }
+ else
+ {
+ Resize(oldWidth, width, false);
+ }
+ }
+ else
+ {
+ SetWidthInternalNoCallback(width);
+ }
+ }
+ }
}
@@ -393,17 +421,7 @@ namespace Avalonia.Controls
}
}
- public Classes CellStyleClasses
- {
- get => _cellStyleClasses;
- set
- {
- if(_cellStyleClasses != value)
- {
- _cellStyleClasses.Replace(value);
- }
- }
- }
+ public Classes CellStyleClasses => _cellStyleClasses ??= new();
///
/// Backing field for CellTheme property.
@@ -559,48 +577,15 @@ namespace Avalonia.Controls
}
}
+ public static readonly StyledProperty WidthProperty = AvaloniaProperty
+ .Register(nameof(Width)
+ , coerce: CoerceWidth
+ );
+
public DataGridLength Width
{
- get
- {
- return
- _width ??
- OwningGrid?.ColumnWidth ??
- // We don't have a good choice here because we don't want to make this property nullable, see DevDiv Bugs 196581
- DataGridLength.Auto;
- }
- set
- {
- if (!_width.HasValue || _width.Value != value)
- {
- if (!_settingWidthInternally)
- {
- InheritsWidth = false;
- }
-
- if (OwningGrid != null)
- {
- DataGridLength width = CoerceWidth(value);
- if (width.IsStar != Width.IsStar)
- {
- // If a column has changed either from or to a star value, we want to recalculate all
- // star column widths. They are recalculated during Measure based off what the value we set here.
- SetWidthInternalNoCallback(width);
- IsInitialDesiredWidthDetermined = false;
- OwningGrid.OnColumnWidthChanged(this);
- }
- else
- {
- // If a column width's value is simply changing, we resize it (to the right only).
- Resize(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue, false);
- }
- }
- else
- {
- SetWidthInternalNoCallback(value);
- }
- }
- }
+ get => this.GetValue(WidthProperty);
+ set => SetValue(WidthProperty, value);
}
///
@@ -703,7 +688,7 @@ namespace Avalonia.Controls
public void ClearSort()
{
//InvokeProcessSort is already validating if sorting is possible
- _headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier());
+ _headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier(OwningGrid));
}
///
@@ -822,19 +807,34 @@ namespace Avalonia.Controls
/// on the rest of the star columns. For pixel widths, the desired value is based on the pixel value.
/// For auto widths, the desired value is initialized as the column's minimum width.
///
+ ///
/// The DataGridLength to coerce.
/// The resultant (coerced) DataGridLength.
- internal DataGridLength CoerceWidth(DataGridLength width)
+ static DataGridLength CoerceWidth(AvaloniaObject source, DataGridLength width)
{
+ var target = (DataGridColumn)source;
+
+ if (target._setWidthInternalNoCallback)
+ {
+ return width;
+ }
+
+ if (!target.IsSet(WidthProperty))
+ {
+
+ return target.OwningGrid?.ColumnWidth ??
+ DataGridLength.Auto;
+ }
+
double desiredValue = width.DesiredValue;
if (double.IsNaN(desiredValue))
{
- if (width.IsStar && OwningGrid != null && OwningGrid.ColumnsInternal != null)
+ if (width.IsStar && target.OwningGrid != null && target.OwningGrid.ColumnsInternal != null)
{
double totalStarValues = 0;
double totalStarDesiredValues = 0;
double totalNonStarDisplayWidths = 0;
- foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != this && !double.IsNaN(c.Width.DesiredValue)))
+ foreach (DataGridColumn column in target.OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != target && !double.IsNaN(c.Width.DesiredValue)))
{
if (column.Width.IsStar)
{
@@ -849,7 +849,7 @@ namespace Avalonia.Controls
if (totalStarValues == 0)
{
// Compute the new star column's desired value based on the available space if there are no other visible star columns
- desiredValue = Math.Max(ActualMinWidth, OwningGrid.CellsWidth - totalNonStarDisplayWidths);
+ desiredValue = Math.Max(target.ActualMinWidth, target.OwningGrid.CellsWidth - totalNonStarDisplayWidths);
}
else
{
@@ -863,7 +863,7 @@ namespace Avalonia.Controls
}
else
{
- desiredValue = ActualMinWidth;
+ desiredValue = target.ActualMinWidth;
}
}
@@ -872,7 +872,7 @@ namespace Avalonia.Controls
{
displayValue = desiredValue;
}
- displayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue));
+ displayValue = Math.Max(target.ActualMinWidth, Math.Min(target.ActualMaxWidth, displayValue));
return new DataGridLength(width.Value, width.UnitType, desiredValue, displayValue);
}
@@ -906,7 +906,7 @@ namespace Avalonia.Controls
};
result[!ContentControl.ContentProperty] = this[!HeaderProperty];
result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty];
- if (OwningGrid.ColumnHeaderTheme is {} columnTheme)
+ if (OwningGrid.ColumnHeaderTheme is { } columnTheme)
{
result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.Template);
}
@@ -919,7 +919,7 @@ namespace Avalonia.Controls
///
internal void EnsureWidth()
{
- SetWidthInternalNoCallback(CoerceWidth(Width));
+ SetWidthInternalNoCallback(CoerceWidth(this, Width));
}
internal Control GenerateElementInternal(DataGridCell cell, object dataItem)
@@ -941,17 +941,17 @@ namespace Avalonia.Controls
/// can only decrease in size by the amount that the columns after it can increase in size.
/// Likewise, the column can only increase in size if other columns can spare the width.
///
- /// The new Value.
- /// The new UnitType.
- /// The new DesiredValue.
- /// The new DisplayValue.
+ /// with before resize.
+ /// with after resize.
/// Whether or not this resize was initiated by a user action.
- internal void Resize(double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue, bool userInitiated)
+
+ // double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue
+ internal void Resize(DataGridLength oldWidth, DataGridLength newWidth, bool userInitiated)
{
- double newValue = value;
- double newDesiredValue = desiredValue;
- double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue));
- DataGridLengthUnitType newUnitType = unitType;
+ double newValue = newWidth.Value;
+ double newDesiredValue = newWidth.DesiredValue;
+ double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, newWidth.DisplayValue));
+ DataGridLengthUnitType newUnitType = newWidth.UnitType;
int starColumnsCount = 0;
double totalDisplayWidth = 0;
@@ -965,11 +965,11 @@ namespace Avalonia.Controls
// If we're using star sizing, we can only resize the column as much as the columns to the
// right will allow (i.e. until they hit their max or min widths).
- if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (unitType == DataGridLengthUnitType.Star && Width.IsStar && userInitiated)))
+ if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (newUnitType == DataGridLengthUnitType.Star && newWidth.IsStar && userInitiated)))
{
- double limitedDisplayValue = Width.DisplayValue;
+ double limitedDisplayValue = oldWidth.DisplayValue;
double availableIncrease = Math.Max(0, OwningGrid.CellsWidth - totalDisplayWidth);
- double desiredChange = newDisplayValue - Width.DisplayValue;
+ double desiredChange = newDisplayValue - oldWidth.DisplayValue;
if (desiredChange > availableIncrease)
{
// The desired change is greater than the amount of available space,
@@ -989,7 +989,7 @@ namespace Avalonia.Controls
// The desired change is negative, so we need to increase the widths of columns to the right.
limitedDisplayValue += desiredChange + OwningGrid.IncreaseColumnWidths(DisplayIndex + 1, -desiredChange, userInitiated);
}
- if (ActualCanUserResize || (Width.IsStar && !userInitiated))
+ if (ActualCanUserResize || (oldWidth.IsStar && !userInitiated))
{
newDisplayValue = limitedDisplayValue;
}
@@ -1012,9 +1012,10 @@ namespace Avalonia.Controls
}
}
- DataGridLength oldWidth = Width;
- SetWidthInternalNoCallback(new DataGridLength(Math.Min(double.MaxValue, newValue), newUnitType, newDesiredValue, newDisplayValue));
- if (Width != oldWidth)
+ newDisplayValue = Math.Min(double.MaxValue, newValue);
+ newWidth = new DataGridLength(newDisplayValue, newUnitType, newDesiredValue, newDisplayValue);
+ SetWidthInternalNoCallback(newWidth);
+ if (newWidth != oldWidth)
{
OwningGrid.OnColumnWidthChanged(this);
}
@@ -1062,7 +1063,17 @@ namespace Avalonia.Controls
/// The new Width.
internal void SetWidthInternalNoCallback(DataGridLength width)
{
- _width = width;
+ var originalValue = _setWidthInternalNoCallback;
+ _setWidthInternalNoCallback = true;
+ try
+ {
+ Width = width;
+ }
+ finally
+ {
+ _setWidthInternalNoCallback = originalValue;
+ }
+
}
///
@@ -1103,6 +1114,16 @@ namespace Avalonia.Controls
get;
set;
}
+
+ ///
+ /// Gets or sets an object associated with this column.
+ ///
+ public object Tag
+ {
+ get;
+ set;
+ }
+
///
/// Holds a Comparer to use for sorting, if not using the default.
///
@@ -1122,7 +1143,7 @@ namespace Avalonia.Controls
&& OwningGrid.DataConnection != null
&& OwningGrid.DataConnection.SortDescriptions != null)
{
- if(CustomSortComparer != null)
+ if (CustomSortComparer != null)
{
return
OwningGrid.DataConnection.SortDescriptions
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnCollection.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnCollection.cs
index e7f9a9a6c4..798228c84c 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumnCollection.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumnCollection.cs
@@ -191,7 +191,7 @@ namespace Avalonia.Controls
}
if (dataGridColumn == null)
{
- throw new ArgumentNullException("dataGridColumn");
+ throw new ArgumentNullException(nameof(dataGridColumn));
}
int columnIndexWithFiller = columnIndex;
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
index ef1e84c745..28e8a0ed5e 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
@@ -241,7 +241,7 @@ namespace Avalonia.Controls
DataGrid owningGrid = OwningGrid;
DataGridSortDescription newSort;
- KeyboardHelper.GetMetaKeyState(keyModifiers, out bool ctrl, out bool shift);
+ KeyboardHelper.GetMetaKeyState(this, keyModifiers, out bool ctrl, out bool shift);
DataGridSortDescription sort = OwningColumn.GetSortDescription();
IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView;
@@ -785,7 +785,9 @@ namespace Avalonia.Controls
double desiredWidth = _originalWidth + mouseDelta;
desiredWidth = Math.Max(_dragColumn.ActualMinWidth, Math.Min(_dragColumn.ActualMaxWidth, desiredWidth));
- _dragColumn.Resize(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth, true);
+ _dragColumn.Resize(_dragColumn.Width,
+ new(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth),
+ true);
OwningGrid.UpdateHorizontalOffset(_originalHorizontalOffset);
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs
index 4056b78bfe..8dd5bd262b 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs
@@ -91,7 +91,10 @@ namespace Avalonia.Controls
// this column is newly added, we'll just set its display value directly.
if (UsesStarSizing && column.IsInitialDesiredWidthDetermined)
{
- column.Resize(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth, false);
+ var oldWidth = column.Width;
+ column.Resize(oldWidth,
+ new(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth),
+ false);
}
else
{
@@ -142,7 +145,7 @@ namespace Avalonia.Controls
{
Debug.Assert(dataGridColumn != null);
- if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn &&
+ if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn &&
dataGridBoundColumn.Binding is BindingBase binding)
{
var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString();
@@ -359,6 +362,7 @@ namespace Avalonia.Controls
if (column.IsVisible && oldValue != column.ActualMaxWidth)
{
+ DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue);
if (column.ActualMaxWidth < column.Width.DisplayValue)
{
// If the maximum width has caused the column to decrease in size, try first to resize
@@ -371,7 +375,9 @@ namespace Avalonia.Controls
{
// If the column was previously limited by its maximum value but has more room now,
// attempt to resize the column to its desired width.
- column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false);
+ column.Resize(oldWitdh,
+ new (column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue),
+ false);
}
OnColumnWidthChanged(column);
}
@@ -388,6 +394,7 @@ namespace Avalonia.Controls
if (column.IsVisible && oldValue != column.ActualMinWidth)
{
+ DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue);
if (column.ActualMinWidth > column.Width.DisplayValue)
{
// If the minimum width has caused the column to increase in size, try first to resize
@@ -400,7 +407,9 @@ namespace Avalonia.Controls
{
// If the column was previously limited by its minimum value but but can be smaller now,
// attempt to resize the column to its desired width.
- column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false);
+ column.Resize(oldWitdh,
+ new(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue),
+ false);
}
OnColumnWidthChanged(column);
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridLength.cs b/src/Avalonia.Controls.DataGrid/DataGridLength.cs
index 4841ddd494..9bff8b2cf5 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridLength.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridLength.cs
@@ -7,7 +7,6 @@ using Avalonia.Utilities;
using System;
using System.ComponentModel;
using System.Globalization;
-using Avalonia.Controls.Utils;
namespace Avalonia.Controls
{
@@ -499,7 +498,7 @@ namespace Avalonia.Controls
{
if (destinationType == null)
{
- throw new ArgumentNullException("destinationType");
+ throw new ArgumentNullException(nameof(destinationType));
}
if (destinationType != typeof(string))
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
index 44079d24d0..c9a348d172 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
@@ -2969,11 +2969,8 @@ namespace Avalonia.Controls
var detailsContent = RowDetailsTemplate.Build(dataItem);
if (detailsContent != null)
{
+ detailsContent.DataContext = dataItem;
_rowsPresenter.Children.Add(detailsContent);
- if (dataItem != null)
- {
- detailsContent.DataContext = dataItem;
- }
detailsContent.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
RowDetailsHeightEstimate = detailsContent.DesiredSize.Height;
_rowsPresenter.Children.Remove(detailsContent);
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
index 082eac60be..2d40721bbf 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
@@ -42,9 +42,10 @@
-
+
0.6
0.8
+ 32
M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z
M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z
@@ -169,26 +170,28 @@
CornerRadius="{TemplateBinding CornerRadius}">
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
();
- return keymap?.CommandModifiers ?? KeyModifiers.Control;
+ public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier(Control target)
+ {
+ var keymap = TopLevel.GetTopLevel(target)!.PlatformSettings!.HotkeyConfiguration;
+ return keymap.CommandModifiers;
}
}
}
diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs
index 77cc9d4dcb..436e618a6b 100644
--- a/src/Avalonia.Controls/AppBuilder.cs
+++ b/src/Avalonia.Controls/AppBuilder.cs
@@ -12,23 +12,23 @@ namespace Avalonia
///
/// Initializes platform-specific services for an .
///
- public class AppBuilder
+ public sealed class AppBuilder
{
private static bool s_setupWasAlreadyCalled;
private Action? _optionsInitializers;
private Func? _appFactory;
private IApplicationLifetime? _lifetime;
-
- ///
- /// Gets or sets the instance.
- ///
- public IRuntimePlatform RuntimePlatform { get; set; }
///
/// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader)
///
- public Action RuntimePlatformServicesInitializer { get; private set; }
+ public Action? RuntimePlatformServicesInitializer { get; private set; }
+ ///
+ /// Gets the name of the currently selected windowing subsystem.
+ ///
+ public string? RuntimePlatformServicesName { get; private set; }
+
///
/// Gets the instance being initialized.
///
@@ -70,21 +70,10 @@ namespace Avalonia
///
/// Initializes a new instance of the class.
///
- public AppBuilder()
- : this(new StandardRuntimePlatform(),
- builder => StandardRuntimePlatformServices.Register(builder.ApplicationType?.Assembly))
+ private AppBuilder()
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- protected AppBuilder(IRuntimePlatform platform, Action platformServices)
- {
- RuntimePlatform = platform;
- RuntimePlatformServicesInitializer = () => platformServices(this);
- }
-
///
/// Begin configuring an .
///
@@ -96,7 +85,6 @@ namespace Avalonia
return new AppBuilder()
{
ApplicationType = typeof(TApp),
- // Needed for CoreRT compatibility
_appFactory = () => new TApp()
};
}
@@ -151,11 +139,11 @@ namespace Avalonia
}
throw new InvalidOperationException(
- $"Unable to create AppBuilder from type {entryPointType.Name}." +
+ $"Unable to create AppBuilder from type \"{entryPointType.FullName}\". " +
$"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type.");
}
- protected AppBuilder Self => this;
+ private AppBuilder Self => this;
public AppBuilder AfterSetup(Action callback)
{
@@ -225,6 +213,30 @@ namespace Avalonia
RenderingSubsystemName = name;
return Self;
}
+
+ ///
+ /// Specifies a runtime platform subsystem to use.
+ ///
+ /// The method to call to initialize the runtime platform subsystem.
+ /// The name of the runtime platform subsystem.
+ /// An instance.
+ public AppBuilder UseRuntimePlatformSubsystem(Action initializer, string name = "")
+ {
+ RuntimePlatformServicesInitializer = initializer;
+ RuntimePlatformServicesName = name;
+ return Self;
+ }
+
+ ///
+ /// Specifies a standard runtime platform subsystem to use.
+ ///
+ /// An instance.
+ public AppBuilder UseStandardRuntimePlatformSubsystem()
+ {
+ RuntimePlatformServicesInitializer = () => StandardRuntimePlatformServices.Register(ApplicationType?.Assembly);
+ RuntimePlatformServicesName = nameof(StandardRuntimePlatform);
+ return Self;
+ }
///
/// Configures platform-specific options
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index b98ad123f1..30791551f5 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -180,6 +180,17 @@ namespace Avalonia
///
public IApplicationLifetime? ApplicationLifetime { get; set; }
+ ///
+ /// Represents a contract for accessing global platform-specific settings.
+ ///
+ ///
+ /// PlatformSettings can be null only if application wasn't initialized yet.
+ /// 's is an equivalent API
+ /// which should always be preferred over a global one,
+ /// as specific top levels might have different settings set-up.
+ ///
+ public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService();
+
event Action>? IGlobalStyles.GlobalStylesAdded
{
add => _stylesAdded += value;
@@ -229,10 +240,12 @@ namespace Avalonia
var focusManager = new FocusManager();
InputManager = new InputManager();
- var settings = AvaloniaLocator.Current.GetRequiredService();
- settings.ColorValuesChanged += OnColorValuesChanged;
- OnColorValuesChanged(settings, settings.GetColorValues());
-
+ if (PlatformSettings is { } settings)
+ {
+ settings.ColorValuesChanged += OnColorValuesChanged;
+ OnColorValuesChanged(settings, settings.GetColorValues());
+ }
+
AvaloniaLocator.CurrentMutable
.Bind().ToTransient()
.Bind().ToConstant(this)
@@ -242,7 +255,7 @@ namespace Avalonia
.Bind().ToConstant(InputManager)
.Bind().ToTransient()
.Bind().ToConstant(DragDropDevice.Instance);
-
+
// TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x
if (AvaloniaLocator.Current.GetService() == null)
AvaloniaLocator.CurrentMutable
diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
index d0b894101f..ebf66164b7 100644
--- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
@@ -1320,17 +1320,14 @@ namespace Avalonia.Controls
// Evaluate the conditions needed for completion.
// 1. Minimum prefix length
// 2. If a delay timer is in use, use it
- bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
- if (populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
- {
- populateReady = false;
- }
- _userCalledPopulate = populateReady ? userInitiated : false;
+ bool minimumLengthReached = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
+
+ _userCalledPopulate = minimumLengthReached && userInitiated;
// Update the interface and values only as necessary
UpdateTextValue(newText, userInitiated);
- if (populateReady)
+ if (minimumLengthReached)
{
_ignoreTextSelectionChange = true;
diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
index 3d3fe35d29..fb7cdd87ed 100644
--- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Avalonia.Automation.Provider;
namespace Avalonia.Automation.Peers
{
@@ -115,9 +116,14 @@ namespace Avalonia.Automation.Peers
///
/// Gets the that is the parent of this .
///
- ///
public AutomationPeer? GetParent() => GetParentCore();
+ ///
+ /// Gets the that is the root of this 's
+ /// visual tree.
+ ///
+ public AutomationPeer? GetVisualRoot() => GetVisualRootCore();
+
///
/// Gets a value that indicates whether the element that is associated with this automation
/// peer currently has keyboard focus.
@@ -247,6 +253,21 @@ namespace Avalonia.Automation.Peers
return GetAutomationControlTypeCore();
}
+ protected virtual AutomationPeer? GetVisualRootCore()
+ {
+ var peer = this;
+ var parent = peer.GetParent();
+
+ while (peer.GetProvider() is null && parent is not null)
+ {
+ peer = parent;
+ parent = peer.GetParent();
+ }
+
+ return peer;
+ }
+
+
protected virtual bool IsContentElementOverrideCore()
{
return IsControlElement() && IsContentElementCore();
diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
index 756951667b..69f267a605 100644
--- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Automation.Peers
public ControlAutomationPeer(Control owner)
{
- Owner = owner ?? throw new ArgumentNullException("owner");
+ Owner = owner ?? throw new ArgumentNullException(nameof(owner));
Initialize();
}
@@ -120,6 +120,13 @@ namespace Avalonia.Automation.Peers
return _parent;
}
+ protected override AutomationPeer? GetVisualRootCore()
+ {
+ if (Owner.GetVisualRoot() is Control c)
+ return CreatePeerForElement(c);
+ return null;
+ }
+
///
/// Invalidates the peer's children and causes a re-read from .
///
diff --git a/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs
index db16bf0a53..64727c43c5 100644
--- a/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs
@@ -28,7 +28,7 @@ namespace Avalonia.Automation.Peers
if (!_searchedForScrollable)
{
if (Owner.GetValue(ListBox.ScrollProperty) is Control scrollable)
- _scroller = GetOrCreate(scrollable) as IScrollProvider;
+ _scroller = GetOrCreate(scrollable).GetProvider();
_searchedForScrollable = true;
}
diff --git a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs
index aea91b5e26..dab8c45567 100644
--- a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Automation.Peers
if (Owner.Parent is Control parent)
{
var parentPeer = GetOrCreate(parent);
- return parentPeer as ISelectionProvider;
+ return parentPeer.GetProvider();
}
return null;
diff --git a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs
index 835ed1c4af..674eb9a241 100644
--- a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs
@@ -146,12 +146,12 @@ namespace Avalonia.Automation.Peers
if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0))
{
- throw new ArgumentOutOfRangeException("horizontalPercent");
+ throw new ArgumentOutOfRangeException(nameof(horizontalPercent));
}
if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0))
{
- throw new ArgumentOutOfRangeException("verticalPercent");
+ throw new ArgumentOutOfRangeException(nameof(verticalPercent));
}
var offset = Owner.Offset;
diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
index 9ec65592fa..ceb695422d 100644
--- a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
+using System.Globalization;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
using Avalonia.Input;
@@ -32,7 +33,21 @@ namespace Avalonia.Automation.Peers
public AutomationPeer? GetPeerFromPoint(Point p)
{
var hit = Owner.GetVisualAt(p)?.FindAncestorOfType(includeSelf: true);
- return hit is object ? GetOrCreate(hit) : null;
+
+ if (hit is null)
+ return null;
+
+ var peer = GetOrCreate(hit);
+
+ while (peer != this && peer.GetProvider() is { } embedded)
+ {
+ var embeddedHit = embedded.GetPeerFromPoint(p);
+ if (embeddedHit is null)
+ break;
+ peer = embeddedHit;
+ }
+
+ return peer;
}
protected void StartTrackingFocus()
diff --git a/src/Avalonia.Controls/Automation/Provider/IEmbeddedRootProvider.cs b/src/Avalonia.Controls/Automation/Provider/IEmbeddedRootProvider.cs
new file mode 100644
index 0000000000..1b1caef182
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/Provider/IEmbeddedRootProvider.cs
@@ -0,0 +1,33 @@
+using System;
+using Avalonia.Automation.Peers;
+
+namespace Avalonia.Automation.Provider
+{
+ ///
+ /// Exposure methods and properties to support UI Automation client access to the root of an
+ /// automation tree hosted by another UI framework.
+ ///
+ ///
+ /// This interface is implemented by the class, and can be used
+ /// to embed an automation tree from a 3rd party UI framework that wishes to use Avalonia's
+ /// automation support.
+ ///
+ public interface IEmbeddedRootProvider
+ {
+ ///
+ /// Gets the currently focused element.
+ ///
+ AutomationPeer? GetFocus();
+
+ ///
+ /// Gets the element at the specified point, expressed in top-level coordinates.
+ ///
+ /// The point.
+ AutomationPeer? GetPeerFromPoint(Point p);
+
+ ///
+ /// Raised by the automation peer when the focus changes.
+ ///
+ event EventHandler? FocusChanged;
+ }
+}
diff --git a/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs b/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs
index ce38059559..6a266da5c5 100644
--- a/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs
+++ b/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs
@@ -4,11 +4,36 @@ using Avalonia.Platform;
namespace Avalonia.Automation.Provider
{
+ ///
+ /// Exposes methods and properties to support UI Automation client access to the root of an
+ /// automation tree.
+ ///
+ ///
+ /// This interface is implemented by the class, and should only
+ /// be implemented on true root elements, such as Windows. To embed an automation tree, use
+ /// instead.
+ ///
public interface IRootProvider
{
+ ///
+ /// Gets the platform implementation of the TopLevel for the element.
+ ///
ITopLevelImpl? PlatformImpl { get; }
+
+ ///
+ /// Gets the currently focused element.
+ ///
AutomationPeer? GetFocus();
+
+ ///
+ /// Gets the element at the specified point, expressed in top-level coordinates.
+ ///
+ /// The point.
AutomationPeer? GetPeerFromPoint(Point p);
+
+ ///
+ /// Raised by the automation peer when the focus changes.
+ ///
event EventHandler? FocusChanged;
}
}
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index bce1a3685f..bd05d4947c 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -270,6 +270,7 @@ namespace Avalonia.Controls
{
if (_popup?.IsInsidePopup(source) == true)
{
+ e.Handled = true;
return;
}
}
@@ -516,5 +517,14 @@ namespace Avalonia.Controls
}
}
}
+
+ ///
+ /// Clears the selection
+ ///
+ public void Clear()
+ {
+ SelectedItem = null;
+ SelectedIndex = -1;
+ }
}
}
diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs
index 867dca2be1..3564ce1faa 100644
--- a/src/Avalonia.Controls/ContentControl.cs
+++ b/src/Avalonia.Controls/ContentControl.cs
@@ -40,11 +40,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty VerticalContentAlignmentProperty =
AvaloniaProperty.Register(nameof(VerticalContentAlignment));
- static ContentControl()
- {
- ContentProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e));
- }
-
///
/// Gets or sets the content to display.
///
@@ -100,6 +95,16 @@ namespace Avalonia.Controls
{
return RegisterContentPresenter(presenter);
}
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ContentProperty)
+ {
+ ContentChanged(change);
+ }
+ }
///
/// Called when an is registered with the control.
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 3dc1514667..78f6c5f77d 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -114,7 +114,6 @@ namespace Avalonia.Controls
///
static ContextMenu()
{
- ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
PlacementProperty.OverrideDefaultValue(PlacementMode.Pointer);
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue(AccessibilityView.Control);
@@ -216,18 +215,23 @@ namespace Avalonia.Controls
if (e.OldValue is ContextMenu oldMenu)
{
control.ContextRequested -= ControlContextRequested;
+ control.AttachedToVisualTree -= ControlOnAttachedToVisualTree;
control.DetachedFromVisualTree -= ControlDetachedFromVisualTree;
oldMenu._attachedControls?.Remove(control);
((ISetLogicalParent?)oldMenu._popup)?.SetParent(null);
}
- if (e.NewValue is ContextMenu newMenu)
+ if (e.NewValue is ContextMenu)
{
- newMenu._attachedControls ??= new List();
- newMenu._attachedControls.Add(control);
control.ContextRequested += ControlContextRequested;
+ control.AttachedToVisualTree += ControlOnAttachedToVisualTree;
control.DetachedFromVisualTree += ControlDetachedFromVisualTree;
}
+
+ if (control.IsAttachedToVisualTree)
+ {
+ AttachControlToContextMenu(control);
+ }
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@@ -405,7 +409,7 @@ namespace Avalonia.Controls
{
if (IsOpen)
{
- var keymap = AvaloniaLocator.Current.GetService();
+ var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true
&& !CancelClosing())
@@ -428,11 +432,25 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
+
+
+ private static void ControlOnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
+ {
+ AttachControlToContextMenu(sender);
+ }
+
+ private static void AttachControlToContextMenu(object? sender)
+ {
+ if (sender is Control { ContextMenu: { } contextMenu } control)
+ {
+ contextMenu._attachedControls ??= new List();
+ contextMenu._attachedControls.Add(control);
+ }
+ }
private static void ControlDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
- if (sender is Control control
- && control.ContextMenu is ContextMenu contextMenu)
+ if (sender is Control { ContextMenu: { } contextMenu } control)
{
if (contextMenu._popup?.Parent == control)
{
@@ -440,6 +458,7 @@ namespace Avalonia.Controls
}
contextMenu.Close();
+ contextMenu._attachedControls?.Remove(control);
}
}
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 8d140bdd62..0851c14178 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -316,7 +316,8 @@ namespace Avalonia.Controls
((ILogical)this).IsAttachedToLogicalTree)
{
_isLoaded = true;
- OnLoaded();
+
+ OnLoaded(new RoutedEventArgs(LoadedEvent, this));
}
}
@@ -333,28 +334,36 @@ namespace Avalonia.Controls
_loadedQueue.Remove(this);
_isLoaded = false;
- OnUnloaded();
+
+ OnUnloaded(new RoutedEventArgs(UnloadedEvent, this));
}
}
///
/// Invoked just before the event.
///
- protected virtual void OnLoaded()
+ /// The event args.
+ protected virtual void OnLoaded(RoutedEventArgs e)
{
- var eventArgs = new RoutedEventArgs(LoadedEvent);
- eventArgs.Source = null;
- RaiseEvent(eventArgs);
+ RaiseEvent(e);
}
///
/// Invoked just before the event.
///
- protected virtual void OnUnloaded()
+ /// The event args.
+ protected virtual void OnUnloaded(RoutedEventArgs e)
{
- var eventArgs = new RoutedEventArgs(UnloadedEvent);
- eventArgs.Source = null;
- RaiseEvent(eventArgs);
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Invoked just before the event.
+ ///
+ /// The event args.
+ protected virtual void OnSizeChanged(SizeChangedEventArgs e)
+ {
+ RaiseEvent(e);
}
///
@@ -435,6 +444,10 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Returns a new, type-specific implementation for the control.
+ ///
+ /// The type-specific implementation.
protected virtual AutomationPeer OnCreateAutomationPeer()
{
return new NoneAutomationPeer(this);
@@ -459,6 +472,7 @@ namespace Avalonia.Controls
return _automationPeer;
}
+ ///
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
@@ -473,6 +487,7 @@ namespace Avalonia.Controls
}
}
+ ///
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
@@ -480,7 +495,7 @@ namespace Avalonia.Controls
if (e.Source == this
&& !e.Handled)
{
- var keymap = AvaloniaLocator.Current.GetService()?.OpenContextMenu;
+ var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration.OpenContextMenu;
if (keymap is null)
{
@@ -531,7 +546,7 @@ namespace Avalonia.Controls
previousSize: new Size(oldValue.Width, oldValue.Height),
newSize: new Size(newValue.Width, newValue.Height));
- RaiseEvent(sizeChangedEventArgs);
+ OnSizeChanged(sizeChangedEventArgs);
}
}
}
diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
index 69b0ffe9a6..5a5c3fdf1d 100644
--- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
@@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives
set
{
if (value > MaximumValue || value < MinimumValue)
- throw new ArgumentOutOfRangeException("SelectedValue");
+ throw new ArgumentOutOfRangeException(nameof(value));
var sel = CoerceSelected(value);
_selectedValue = sel;
@@ -195,7 +195,7 @@ namespace Avalonia.Controls.Primitives
set
{
if (value <= 0 || value > _range)
- throw new ArgumentOutOfRangeException("Increment");
+ throw new ArgumentOutOfRangeException(nameof(value));
_increment = value;
UpdateHelperInfo();
var sel = CoerceSelected(SelectedValue);
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index f379120638..106ac8dff5 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Raw;
-using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
-using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
-using Avalonia.Threading;
namespace Avalonia.Controls.Embedding.Offscreen
{
@@ -17,7 +13,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
private double _scaling = 1;
private Size _clientSize;
- private ManualRenderTimer _manualRenderTimer = new();
public IInputRoot? InputRoot { get; private set; }
public bool IsDisposed { get; private set; }
@@ -27,21 +22,10 @@ namespace Avalonia.Controls.Embedding.Offscreen
IsDisposed = true;
}
- class ManualRenderTimer : IRenderTimer
- {
- static Stopwatch St = Stopwatch.StartNew();
- public event Action? Tick;
- public bool RunsInBackground => false;
- public void TriggerTick() => Tick?.Invoke(St.Elapsed);
- }
-
public Compositor Compositor { get; }
public OffscreenTopLevelImplBase()
- {
- Compositor = new Compositor(new RenderLoop(_manualRenderTimer), null, false,
- MediaContext.Instance, false);
- }
+ => Compositor = new Compositor(null);
public abstract IEnumerable Surfaces { get; }
@@ -76,7 +60,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
- ///
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
index 7fd9fad605..c3cf1f4897 100644
--- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
@@ -9,6 +9,7 @@ using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.Reactive;
+using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@@ -392,7 +393,7 @@ namespace Avalonia.Controls.Primitives
&& IsOpen
&& Target?.ContextFlyout == this)
{
- var keymap = AvaloniaLocator.Current.GetService();
+ var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true)
{
diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs
index de753f0bd0..6ad4a8cc76 100644
--- a/src/Avalonia.Controls/HotkeyManager.cs
+++ b/src/Avalonia.Controls/HotkeyManager.cs
@@ -149,10 +149,10 @@ namespace Avalonia.Controls
return;
var control = args.Sender as Control;
- if (control is not IClickableControl)
+ if (control is not IClickableControl and not ICommandSource)
{
Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.Log(control,
- $"The element {args.Sender.GetType().Name} does not implement IClickableControl and does not support binding a HotKey ({args.NewValue}).");
+ $"The element {args.Sender.GetType().Name} does not implement IClickableControl nor ICommandSource and does not support binding a HotKey ({args.NewValue}).");
return;
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 06427991f8..83aa88a7b6 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Controls
/// Displays a collection of items.
///
[PseudoClasses(":empty", ":singleitem")]
- public class ItemsControl : TemplatedControl, IChildIndexProvider, IScrollSnapPointsInfo
+ public class ItemsControl : TemplatedControl, IChildIndexProvider
{
///
/// The default value for the property.
@@ -67,18 +67,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty DisplayMemberBindingProperty =
AvaloniaProperty.Register(nameof(DisplayMemberBinding));
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty =
- AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular));
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty AreVerticalSnapPointsRegularProperty =
- AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular));
-
///
/// Gets or sets the to use for binding to the display member of each item.
///
@@ -249,64 +237,6 @@ namespace Avalonia.Controls
///
public event EventHandler? ContainerClearing;
- ///
- public event EventHandler HorizontalSnapPointsChanged
- {
- add
- {
- if (_itemsPresenter != null)
- {
- _itemsPresenter.HorizontalSnapPointsChanged += value;
- }
- }
-
- remove
- {
- if (_itemsPresenter != null)
- {
- _itemsPresenter.HorizontalSnapPointsChanged -= value;
- }
- }
- }
-
- ///
- public event EventHandler VerticalSnapPointsChanged
- {
- add
- {
- if (_itemsPresenter != null)
- {
- _itemsPresenter.VerticalSnapPointsChanged += value;
- }
- }
-
- remove
- {
- if (_itemsPresenter != null)
- {
- _itemsPresenter.VerticalSnapPointsChanged -= value;
- }
- }
- }
-
- ///
- /// Gets or sets whether the horizontal snap points for the are equidistant from each other.
- ///
- public bool AreHorizontalSnapPointsRegular
- {
- get => GetValue(AreHorizontalSnapPointsRegularProperty);
- set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
- }
-
- ///
- /// Gets or sets whether the vertical snap points for the are equidistant from each other.
- ///
- public bool AreVerticalSnapPointsRegular
- {
- get => GetValue(AreVerticalSnapPointsRegularProperty);
- set => SetValue(AreVerticalSnapPointsRegularProperty, value);
- }
-
///
/// Gets a default recycle key that can be used when an supports
/// a single container type.
@@ -896,19 +826,5 @@ namespace Avalonia.Controls
count = ItemsView.Count;
return true;
}
-
- ///
- public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
- {
- return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List();
- }
-
- ///
- public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
- {
- offset = 0;
-
- return _itemsPresenter?.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset) ?? 0;
- }
}
}
diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs
index 06069a897e..5f8d4a561d 100644
--- a/src/Avalonia.Controls/LayoutTransformControl.cs
+++ b/src/Avalonia.Controls/LayoutTransformControl.cs
@@ -215,7 +215,7 @@ namespace Avalonia.Controls
///
/// Transformation matrix corresponding to _matrixTransform.
///
- private Matrix _transformation;
+ private Matrix _transformation = Matrix.Identity;
private IDisposable? _transformChangedEvent;
///
@@ -256,13 +256,16 @@ namespace Avalonia.Controls
///
private void ApplyLayoutTransform()
{
- if (LayoutTransform == null)
- return;
-
// Get the transform matrix and apply it
- _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
+ var matrix = LayoutTransform is null ?
+ Matrix.Identity :
+ RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
+
+ if (_transformation == matrix)
+ return;
- _matrixTransform.Matrix = _transformation;
+ _transformation = matrix;
+ _matrixTransform.Matrix = matrix;
// New transform means re-layout is necessary
InvalidateMeasure();
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index f3f0b135b8..2f3ba33d8d 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -9,6 +9,7 @@ using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -127,7 +128,7 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e)
{
- var hotkeys = AvaloniaLocator.Current.GetService();
+ var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
if (!ctrl &&
@@ -147,7 +148,7 @@ namespace Avalonia.Controls
}
else if (e.Key == Key.Space || e.Key == Key.Enter)
{
- e.Handled |= UpdateSelectionFromEventSource(
+ UpdateSelectionFromEventSource(
e.Source,
true,
e.KeyModifiers.HasFlag(KeyModifiers.Shift),
@@ -165,7 +166,7 @@ namespace Avalonia.Controls
internal bool UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e)
{
- var hotkeys = AvaloniaLocator.Current.GetService();
+ var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
var toggle = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
return UpdateSelectionFromEventSource(
diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs
index c2044661e2..15136e5992 100644
--- a/src/Avalonia.Controls/ListBoxItem.cs
+++ b/src/Avalonia.Controls/ListBoxItem.cs
@@ -90,7 +90,7 @@ namespace Avalonia.Controls
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right)
{
var point = e.GetCurrentPoint(this);
- var settings = AvaloniaLocator.Current.GetService();
+ var settings = TopLevel.GetTopLevel(e.Source as Visual)?.PlatformSettings;
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(_pointerDownPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs
index 4800b7b1e4..10eae5e7c0 100644
--- a/src/Avalonia.Controls/MaskedTextBox.cs
+++ b/src/Avalonia.Controls/MaskedTextBox.cs
@@ -7,6 +7,7 @@ using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Styling;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -204,7 +205,7 @@ namespace Avalonia.Controls
return;
}
- var keymap = AvaloniaLocator.Current.GetService();
+ var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 1bb53f90a8..40d6b6b0f4 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -394,6 +394,14 @@ namespace Avalonia.Controls
_isEmbeddedInMenu = parent?.FindLogicalAncestorOfType(true) != null;
}
+ ///
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ TryUpdateCanExecute();
+ }
+
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
// This will cause the hotkey manager to dispose the observer and the reference to this control
diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
index b5be12fa54..84772e7789 100644
--- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
@@ -126,6 +126,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner();
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty TextAlignmentProperty =
+ TextBox.TextAlignmentProperty.AddOwner();
+
private IDisposable? _textBoxTextChangedSubscription;
private bool _internalValueSet;
@@ -299,6 +305,15 @@ namespace Avalonia.Controls
set => SetValue(VerticalContentAlignmentProperty, value);
}
+ ///
+ /// Gets or sets the of the
+ ///
+ public Media.TextAlignment TextAlignment
+ {
+ get => GetValue(TextAlignmentProperty);
+ set => SetValue(TextAlignmentProperty, value);
+ }
+
///
/// Initializes new instance of class.
///
@@ -1113,11 +1128,11 @@ namespace Avalonia.Controls
}
if (value < Minimum)
{
- throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be greater than Minimum value of {0}", Minimum));
+ throw new ArgumentOutOfRangeException(nameof(value), $"Value must be greater than Minimum value of {Minimum}");
}
else if (value > Maximum)
{
- throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be less than Maximum value of {0}", Maximum));
+ throw new ArgumentOutOfRangeException(nameof(value), $"Value must be less than Maximum value of {Maximum}");
}
}
diff --git a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
index fdc098777a..c8225f775b 100644
--- a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
+++ b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
@@ -99,9 +99,15 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
continue;
}
- if (_nextTimer != null)
+ TimeSpan? nextTimer;
+ lock (_lock)
+ {
+ nextTimer = _nextTimer;
+ }
+
+ if (nextTimer != null)
{
- var waitFor = _clock.Elapsed - _nextTimer.Value;
+ var waitFor = nextTimer.Value - _clock.Elapsed;
if (waitFor.TotalMilliseconds < 1)
continue;
_wakeup.WaitOne(waitFor);
@@ -112,4 +118,4 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
registration.Dispose();
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
index 0a7daeaa24..22ee322e36 100644
--- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
+++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
@@ -1,10 +1,17 @@
-using Avalonia.Metadata;
+using System;
+using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform.Surfaces
{
[Unstable]
public interface IFramebufferPlatformSurface
+ {
+ IFramebufferRenderTarget CreateFramebufferRenderTarget();
+ }
+
+ [Unstable]
+ public interface IFramebufferRenderTarget : IDisposable
{
///
/// Provides a framebuffer descriptor for drawing.
@@ -14,4 +21,24 @@ namespace Avalonia.Controls.Platform.Surfaces
///
ILockedFramebuffer Lock();
}
+
+ ///
+ /// For simple cases when framebuffer is always available
+ ///
+ public class FuncFramebufferRenderTarget : IFramebufferRenderTarget
+ {
+ private readonly Func _lockFramebuffer;
+
+ public FuncFramebufferRenderTarget(Func lockFramebuffer)
+ {
+ _lockFramebuffer = lockFramebuffer;
+ }
+
+ public void Dispose()
+ {
+ // No-op
+ }
+
+ public ILockedFramebuffer Lock() => _lockFramebuffer();
+ }
}
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
index 9610be088b..63e512492e 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Controls.Presenters
///
/// Presents items inside an .
///
- public class ItemsPresenter : Control, ILogicalScrollable, IScrollSnapPointsInfo
+ public class ItemsPresenter : Control, ILogicalScrollable
{
///
/// Defines the property.
@@ -21,37 +21,8 @@ namespace Avalonia.Controls.Presenters
private PanelContainerGenerator? _generator;
private ILogicalScrollable? _logicalScrollable;
- private IScrollSnapPointsInfo? _scrollSnapPointsInfo;
private EventHandler? _scrollInvalidated;
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty =
- AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular));
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty AreVerticalSnapPointsRegularProperty =
- AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular));
-
- ///
- /// Defines the event.
- ///
- public static readonly RoutedEvent HorizontalSnapPointsChangedEvent =
- RoutedEvent.Register(
- nameof(HorizontalSnapPointsChanged),
- RoutingStrategies.Bubble);
-
- ///
- /// Defines the event.
- ///
- public static readonly RoutedEvent VerticalSnapPointsChangedEvent =
- RoutedEvent.Register(
- nameof(VerticalSnapPointsChanged),
- RoutingStrategies.Bubble);
-
event EventHandler? ILogicalScrollable.ScrollInvalidated
{
add => _scrollInvalidated += value;
@@ -107,47 +78,12 @@ namespace Avalonia.Controls.Presenters
}
}
- ///
- /// Occurs when the measurements for horizontal snap points change.
- ///
- public event EventHandler? HorizontalSnapPointsChanged
- {
- add => AddHandler(HorizontalSnapPointsChangedEvent, value);
- remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value);
- }
-
- ///
- /// Occurs when the measurements for vertical snap points change.
- ///
- public event EventHandler? VerticalSnapPointsChanged
- {
- add => AddHandler(VerticalSnapPointsChangedEvent, value);
- remove => RemoveHandler(VerticalSnapPointsChangedEvent, value);
- }
-
bool ILogicalScrollable.IsLogicalScrollEnabled => _logicalScrollable?.IsLogicalScrollEnabled ?? false;
Size ILogicalScrollable.ScrollSize => _logicalScrollable?.ScrollSize ?? default;
Size ILogicalScrollable.PageScrollSize => _logicalScrollable?.PageScrollSize ?? default;
Size IScrollable.Extent => _logicalScrollable?.Extent ?? default;
Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default;
- ///
- /// Gets or sets whether the horizontal snap points for the are equidistant from each other.
- ///
- public bool AreHorizontalSnapPointsRegular
- {
- get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
- set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
- }
-
- ///
- /// Gets or sets whether the vertical snap points for the are equidistant from each other.
- ///
- public bool AreVerticalSnapPointsRegular
- {
- get { return GetValue(AreVerticalSnapPointsRegularProperty); }
- set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
- }
public override sealed void ApplyTemplate()
{
@@ -167,36 +103,14 @@ namespace Avalonia.Controls.Presenters
Panel.TemplatedParent = TemplatedParent;
Panel.IsItemsHost = true;
- _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
LogicalChildren.Add(Panel);
VisualChildren.Add(Panel);
- if (_scrollSnapPointsInfo != null)
- {
- _scrollSnapPointsInfo.AreVerticalSnapPointsRegular = AreVerticalSnapPointsRegular;
- _scrollSnapPointsInfo.AreHorizontalSnapPointsRegular = AreHorizontalSnapPointsRegular;
- }
-
if (Panel is VirtualizingPanel v)
v.Attach(ItemsControl);
else
CreateSimplePanelGenerator();
- if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
- {
- scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) =>
- {
- e.RoutedEvent = VerticalSnapPointsChangedEvent;
- RaiseEvent(e);
- };
-
- scrollSnapPointsInfo.HorizontalSnapPointsChanged += (s, e) =>
- {
- e.RoutedEvent = HorizontalSnapPointsChangedEvent;
- RaiseEvent(e);
- };
- }
-
_logicalScrollable = Panel as ILogicalScrollable;
if (_logicalScrollable is not null)
@@ -240,16 +154,6 @@ namespace Avalonia.Controls.Presenters
ResetState();
InvalidateMeasure();
}
- else if(change.Property == AreHorizontalSnapPointsRegularProperty)
- {
- if (_scrollSnapPointsInfo != null)
- _scrollSnapPointsInfo.AreHorizontalSnapPointsRegular = AreHorizontalSnapPointsRegular;
- }
- else if (change.Property == AreVerticalSnapPointsRegularProperty)
- {
- if (_scrollSnapPointsInfo != null)
- _scrollSnapPointsInfo.AreVerticalSnapPointsRegular = AreVerticalSnapPointsRegular;
- }
}
internal void Refresh()
@@ -303,27 +207,5 @@ namespace Avalonia.Controls.Presenters
}
private void OnLogicalScrollInvalidated(object? sender, EventArgs e) => _scrollInvalidated?.Invoke(this, e);
-
- public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
- {
- if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
- {
- return scrollSnapPointsInfo.GetIrregularSnapPoints(orientation, snapPointsAlignment);
- }
-
- return new List();
- }
-
- public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
- {
- if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
- {
- return scrollSnapPointsInfo.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset);
- }
-
- offset = 0;
-
- return 0;
- }
}
}
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 1c9dd79b13..4ff8e7cfef 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -7,6 +7,7 @@ using Avalonia.Input.GestureRecognizers;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Linq;
+using Avalonia.Layout;
namespace Avalonia.Controls.Presenters
{
@@ -99,6 +100,8 @@ namespace Avalonia.Controls.Presenters
private double _horizontalSnapPointOffset;
private CompositeDisposable? _ownerSubscriptions;
private ScrollViewer? _owner;
+ private IScrollSnapPointsInfo? _scrollSnapPointsInfo;
+ private bool _isSnapPointsUpdated;
///
/// Initializes static members of the class.
@@ -377,6 +380,13 @@ namespace Avalonia.Controls.Presenters
CanVerticallyScroll ? double.PositiveInfinity : availableSize.Height);
Child.Measure(constraint);
+
+ if (!_isSnapPointsUpdated)
+ {
+ _isSnapPointsUpdated = true;
+ UpdateSnapPoints();
+ }
+
return Child.DesiredSize.Constrain(availableSize);
}
@@ -463,12 +473,33 @@ namespace Avalonia.Controls.Presenters
}
Viewport = finalSize;
- Extent = Child!.Bounds.Size.Inflate(Child.Margin);
+ Extent = ComputeExtent(finalSize);
_isAnchorElementDirty = true;
return finalSize;
}
+ private Size ComputeExtent(Size viewportSize)
+ {
+ var childMargin = Child!.Margin;
+
+ if (Child.UseLayoutRounding)
+ {
+ var scale = LayoutHelper.GetLayoutScale(Child);
+ childMargin = LayoutHelper.RoundLayoutThickness(childMargin, scale, scale);
+ }
+
+ var extent = Child!.Bounds.Size.Inflate(childMargin);
+
+ if (MathUtilities.AreClose(extent.Width, viewportSize.Width, LayoutHelper.LayoutEpsilon))
+ extent = extent.WithWidth(viewportSize.Width);
+
+ if (MathUtilities.AreClose(extent.Height, viewportSize.Height, LayoutHelper.LayoutEpsilon))
+ extent = extent.WithHeight(viewportSize.Height);
+
+ return extent;
+ }
+
private void OnScrollGesture(object? sender, ScrollGestureEventArgs e)
{
if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width)
@@ -570,7 +601,12 @@ namespace Avalonia.Controls.Presenters
private void OnScrollGestureInertiaStartingEnded(object? sender, ScrollGestureInertiaStartingEventArgs e)
{
- if (Content is not IScrollSnapPointsInfo)
+ var scrollable = Content;
+
+ if (Content is ItemsControl itemsControl)
+ scrollable = itemsControl.Presenter?.Panel;
+
+ if (scrollable is not IScrollSnapPointsInfo)
return;
if (_scrollGestureSnapPoints == null)
@@ -676,22 +712,6 @@ namespace Avalonia.Controls.Presenters
_owner?.SetCurrentValue(OffsetProperty, change.GetNewValue());
}
- else if (change.Property == ContentProperty)
- {
- if (change.OldValue is IScrollSnapPointsInfo oldSnapPointsInfo)
- {
- oldSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
- oldSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
- }
-
- if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo)
- {
- scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
- scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
- }
-
- UpdateSnapPoints();
- }
else if (change.Property == ChildProperty)
{
ChildChanged(change);
@@ -875,7 +895,9 @@ namespace Avalonia.Controls.Presenters
private void UpdateSnapPoints()
{
- if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo)
+ var scrollable = GetScrollSnapPointsInfo(Content);
+
+ if (scrollable is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
_areVerticalSnapPointsRegular = scrollSnapPointsInfo.AreVerticalSnapPointsRegular;
_areHorizontalSnapPointsRegular = scrollSnapPointsInfo.AreHorizontalSnapPointsRegular;
@@ -910,7 +932,9 @@ namespace Avalonia.Controls.Presenters
private Vector SnapOffset(Vector offset)
{
- if(Content is not IScrollSnapPointsInfo)
+ var scrollable = GetScrollSnapPointsInfo(Content);
+
+ if(scrollable is null)
return offset;
var diff = GetAlignedDiff();
@@ -1012,5 +1036,37 @@ namespace Avalonia.Controls.Presenters
}
return snapPoints[Math.Min(point, snapPoints.Count - 1)];
}
+
+ private IScrollSnapPointsInfo? GetScrollSnapPointsInfo(object? content)
+ {
+ var scrollable = content;
+
+ if (Content is ItemsControl itemsControl)
+ scrollable = itemsControl.Presenter?.Panel;
+
+ if (Content is ItemsPresenter itemsPresenter)
+ scrollable = itemsPresenter.Panel;
+
+ var snapPointsInfo = scrollable as IScrollSnapPointsInfo;
+
+ if(snapPointsInfo != _scrollSnapPointsInfo)
+ {
+ if(_scrollSnapPointsInfo != null)
+ {
+ _scrollSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
+ _scrollSnapPointsInfo.HorizontalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
+ }
+
+ _scrollSnapPointsInfo = snapPointsInfo;
+
+ if(_scrollSnapPointsInfo != null)
+ {
+ _scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
+ _scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
+ }
+ }
+
+ return snapPointsInfo;
+ }
}
}
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index c5b4834ded..43c4e76aba 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -50,15 +50,6 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty PreeditTextProperty =
AvaloniaProperty.Register(nameof(PreeditText));
- ///
- /// Defines the property.
- ///
- public static readonly DirectProperty CompositionRegionProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(CompositionRegion),
- o => o.CompositionRegion,
- (o, v) => o.CompositionRegion = v);
-
///
/// Defines the property.
///
@@ -97,7 +88,6 @@ namespace Avalonia.Controls.Presenters
private CharacterHit _lastCharacterHit;
private Rect _caretBounds;
private Point _navigationPosition;
- private TextRange? _compositionRegion;
static TextPresenter()
{
@@ -137,12 +127,6 @@ namespace Avalonia.Controls.Presenters
set => SetValue(PreeditTextProperty, value);
}
- public TextRange? CompositionRegion
- {
- get => _compositionRegion;
- set => SetAndRaise(CompositionRegionProperty, ref _compositionRegion, value);
- }
-
///
/// Gets or sets the font family.
///
@@ -490,10 +474,10 @@ namespace Avalonia.Controls.Presenters
{
TextLayout result;
- var text = Text;
-
+ var caretIndex = CaretIndex;
+ var preeditText = PreeditText;
+ var text = GetCombinedText(Text, caretIndex, preeditText);
var typeface = new Typeface(FontFamily, FontStyle, FontWeight);
-
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
@@ -503,22 +487,9 @@ namespace Avalonia.Controls.Presenters
var foreground = Foreground;
- if (_compositionRegion != null)
- {
- var preeditHighlight = new ValueSpan(_compositionRegion?.Start ?? 0, _compositionRegion?.Length ?? 0,
- new GenericTextRunProperties(typeface, FontSize,
- foregroundBrush: foreground,
- textDecorations: TextDecorations.Underline));
-
- textStyleOverrides = new[]
- {
- preeditHighlight
- };
-
- }
- else if (!string.IsNullOrEmpty(PreeditText))
+ if (!string.IsNullOrEmpty(preeditText))
{
- var preeditHighlight = new ValueSpan(CaretIndex, PreeditText.Length,
+ var preeditHighlight = new ValueSpan(caretIndex, preeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
@@ -554,6 +525,27 @@ namespace Avalonia.Controls.Presenters
return result;
}
+ private static string? GetCombinedText(string? text, int caretIndex, string? preeditText)
+ {
+ if (string.IsNullOrEmpty(preeditText))
+ {
+ return text;
+ }
+
+ if (string.IsNullOrEmpty(text))
+ {
+ return preeditText;
+ }
+
+ var sb = StringBuilderCache.Acquire(text.Length + preeditText.Length);
+
+ sb.Append(text.Substring(0, caretIndex));
+ sb.Insert(caretIndex, preeditText);
+ sb.Append(text.Substring(caretIndex));
+
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
protected virtual void InvalidateTextLayout()
{
_textLayout?.Dispose();
@@ -831,6 +823,18 @@ namespace Avalonia.Controls.Presenters
_caretTimer.Tick -= CaretTimerTick;
}
+ private void OnPreeditTextChanged(string? preeditText)
+ {
+ if (string.IsNullOrEmpty(preeditText))
+ {
+ UpdateCaret(new CharacterHit(CaretIndex), false);
+ }
+ else
+ {
+ UpdateCaret(new CharacterHit(CaretIndex + preeditText.Length), false);
+ }
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -840,10 +844,30 @@ namespace Avalonia.Controls.Presenters
MoveCaretToTextPosition(change.GetNewValue());
}
+ if(change.Property == PreeditTextProperty)
+ {
+ OnPreeditTextChanged(change.NewValue as string);
+ }
+
+ if(change.Property == TextProperty)
+ {
+ if (!string.IsNullOrEmpty(PreeditText))
+ {
+ SetCurrentValue(PreeditTextProperty, null);
+ }
+ }
+
+ if(change.Property == CaretIndexProperty)
+ {
+ if (!string.IsNullOrEmpty(PreeditText))
+ {
+ SetCurrentValue(PreeditTextProperty, null);
+ }
+ }
+
switch (change.Property.Name)
{
case nameof(PreeditText):
- case nameof(CompositionRegion):
case nameof(Foreground):
case nameof(FontSize):
case nameof(FontStyle):
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 76b56f3a11..f9542c883a 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -6,6 +6,7 @@ namespace Avalonia.Controls.Primitives
{
public class OverlayLayer : Canvas
{
+ protected override bool BypassFlowDirectionPolicies => true;
public Size AvailableSize { get; private set; }
public static OverlayLayer? GetOverlayLayer(Visual visual)
{
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index 2236d87dd1..5dd0f23e8e 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -120,11 +120,11 @@ namespace Avalonia.Controls.Primitives
void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize)
{
_lastRequestedPosition = devicePoint;
- Dispatcher.UIThread.Post(() =>
+ MediaContext.Instance.BeginInvokeOnRender(() =>
{
- OverlayLayer.SetLeft(this, _lastRequestedPosition.X);
- OverlayLayer.SetTop(this, _lastRequestedPosition.Y);
- }, DispatcherPriority.Render);
+ Canvas.SetLeft(this, _lastRequestedPosition.X);
+ Canvas.SetTop(this, _lastRequestedPosition.Y);
+ });
}
double IManagedPopupPositionerPopup.Scaling => 1;
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
index 0c9bb89caa..4029782772 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
@@ -216,7 +216,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
///
/// If the adjusted position also ends up being constrained, the resulting position of the
/// FlipX adjustment will be the one before the adjustment.
- /// ///
+ ///
FlipX = 4,
///
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 0e627c2a37..66ed1a27e2 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -5,9 +5,7 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Xml.Linq;
using Avalonia.Controls.Selection;
-using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
@@ -171,7 +169,7 @@ namespace Avalonia.Controls.Primitives
///
public event EventHandler? SelectionChanged
{
- add => AddHandler(SelectionChangedEvent, value);
+ add => AddHandler(SelectionChangedEvent, value);
remove => RemoveHandler(SelectionChangedEvent, value);
}
@@ -369,7 +367,7 @@ namespace Avalonia.Controls.Primitives
///
public bool WrapSelection
{
- get => GetValue(WrapSelectionProperty);
+ get => GetValue(WrapSelectionProperty);
set => SetValue(WrapSelectionProperty, value);
}
@@ -382,7 +380,7 @@ namespace Avalonia.Controls.Primitives
///
protected SelectionMode SelectionMode
{
- get => GetValue(SelectionModeProperty);
+ get => GetValue(SelectionModeProperty);
set => SetValue(SelectionModeProperty, value);
}
@@ -465,7 +463,10 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
- AutoScrollToSelectedItemIfNecessary();
+ if (Selection?.AnchorIndex is int index)
+ {
+ AutoScrollToSelectedItemIfNecessary(index);
+ }
}
///
@@ -476,7 +477,10 @@ namespace Avalonia.Controls.Primitives
void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e)
{
LayoutUpdated -= ExecuteScrollWhenLayoutUpdated;
- AutoScrollToSelectedItemIfNecessary();
+ if (Selection?.AnchorIndex is int index)
+ {
+ AutoScrollToSelectedItemIfNecessary(index);
+ }
}
if (AutoScrollToSelectedItem)
@@ -529,7 +533,16 @@ namespace Avalonia.Controls.Primitives
protected internal override void ClearContainerForItemOverride(Control element)
{
base.ClearContainerForItemOverride(element);
- element.ClearValue(IsSelectedProperty);
+
+ try
+ {
+ _ignoreContainerSelectionChanged = true;
+ element.ClearValue(IsSelectedProperty);
+ }
+ finally
+ {
+ _ignoreContainerSelectionChanged = false;
+ }
}
///
@@ -625,7 +638,10 @@ namespace Avalonia.Controls.Primitives
if (change.Property == AutoScrollToSelectedItemProperty)
{
- AutoScrollToSelectedItemIfNecessary();
+ if (Selection?.AnchorIndex is int index)
+ {
+ AutoScrollToSelectedItemIfNecessary(index);
+ }
}
else if (change.Property == SelectionModeProperty && _selection is object)
{
@@ -909,8 +925,11 @@ namespace Avalonia.Controls.Primitives
if (e.PropertyName == nameof(ISelectionModel.AnchorIndex))
{
_hasScrolledToSelectedItem = false;
- KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(Selection.AnchorIndex));
- AutoScrollToSelectedItemIfNecessary();
+ if (Selection?.AnchorIndex is int index)
+ {
+ KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(index));
+ AutoScrollToSelectedItemIfNecessary(index);
+ }
}
else if (e.PropertyName == nameof(ISelectionModel.SelectedIndex) && _oldSelectedIndex != SelectedIndex)
{
@@ -1038,7 +1057,7 @@ namespace Avalonia.Controls.Primitives
return value;
}
else
- {
+ {
return AvaloniaProperty.UnsetValue;
}
}
@@ -1096,16 +1115,19 @@ namespace Avalonia.Controls.Primitives
}
}
- private void AutoScrollToSelectedItemIfNecessary()
+ private void AutoScrollToSelectedItemIfNecessary(int anchorIndex)
{
if (AutoScrollToSelectedItem &&
!_hasScrolledToSelectedItem &&
Presenter is object &&
- Selection.AnchorIndex >= 0 &&
+ anchorIndex >= 0 &&
IsAttachedToVisualTree)
{
- ScrollIntoView(Selection.AnchorIndex);
- _hasScrolledToSelectedItem = true;
+ Dispatcher.UIThread.Post(state =>
+ {
+ ScrollIntoView((int)state!);
+ _hasScrolledToSelectedItem = true;
+ }, anchorIndex);
}
}
@@ -1373,10 +1395,8 @@ namespace Avalonia.Controls.Primitives
public object Evaluate(object? dataContext)
{
- dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
-
// Only update the DataContext if necessary
- if (!dataContext.Equals(DataContext))
+ if (!Equals(dataContext, DataContext))
DataContext = dataContext;
return GetValue(ValueProperty);
diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs
index c5d73dc00f..7299300cc7 100644
--- a/src/Avalonia.Controls/Primitives/Thumb.cs
+++ b/src/Avalonia.Controls/Primitives/Thumb.cs
@@ -113,6 +113,8 @@ namespace Avalonia.Controls.Primitives
PseudoClasses.Add(":pressed");
+ e.PreventGestureRecognition();
+
RaiseEvent(ev);
}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
index 5c7071a161..c49043ea46 100644
--- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
@@ -223,7 +223,7 @@ namespace Avalonia.Controls
var visualizerVisual = ElementComposition.GetElementVisual(this);
if (visual != null && contentVisual != null && visualizerVisual != null)
{
- contentVisual.CenterPoint = new Vector3((float)(_content.Bounds.Width / 2), (float)(_content.Bounds.Height / 2), 0);
+ contentVisual.CenterPoint = new Vector3D((_content.Bounds.Width / 2), (_content.Bounds.Height / 2), 0);
switch (RefreshVisualizerState)
{
case RefreshVisualizerState.Idle:
@@ -236,44 +236,43 @@ namespace Avalonia.Controls
contentVisual.Opacity = MinimumIndicatorOpacity;
contentVisual.RotationAngle = _startingRotationAngle;
visualizerVisual.Offset = IsPullDirectionVertical ?
- new Vector3(visualizerVisual.Offset.X, 0, 0) :
- new Vector3(0, visualizerVisual.Offset.Y, 0);
- visual.Offset = default;
+ new Vector3D(visualizerVisual.Offset.X, 0, 0) :
+ new Vector3D(0, visualizerVisual.Offset.Y, 0);
_content.InvalidateMeasure();
break;
case RefreshVisualizerState.Interacting:
_played = false;
contentVisual.Opacity = MinimumIndicatorOpacity;
contentVisual.RotationAngle = (float)(_startingRotationAngle + _interactionRatio * 2 * Math.PI);
- Vector3 offset = default;
+ Vector3D offset = default;
if (IsPullDirectionVertical)
{
- offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
+ offset = new Vector3D(0, (_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
}
else
{
- offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
+ offset = new Vector3D((_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
}
visual.Offset = offset;
visualizerVisual.Offset = IsPullDirectionVertical ?
- new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
- new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
+ new Vector3D(visualizerVisual.Offset.X, offset.Y, 0) :
+ new Vector3D(offset.X, visualizerVisual.Offset.Y, 0);
break;
case RefreshVisualizerState.Pending:
contentVisual.Opacity = 1;
contentVisual.RotationAngle = _startingRotationAngle + (float)(2 * Math.PI);
if (IsPullDirectionVertical)
{
- offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
+ offset = new Vector3D(0, (_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
}
else
{
- offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
+ offset = new Vector3D((_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
}
visual.Offset = offset;
visualizerVisual.Offset = IsPullDirectionVertical ?
- new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
- new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
+ new Vector3D(visualizerVisual.Offset.X, offset.Y, 0) :
+ new Vector3D(offset.X, visualizerVisual.Offset.Y, 0);
if (!_played)
{
@@ -302,18 +301,18 @@ namespace Avalonia.Controls
* (IsPullDirectionFar ? -1f : 1f);
if (IsPullDirectionVertical)
{
- offset = new Vector3(0, (float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
+ offset = new Vector3D(0, (_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
}
else
{
- offset = new Vector3((float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
+ offset = new Vector3D((_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
}
visual.Offset = offset;
- contentVisual.Offset += IsPullDirectionVertical ? new Vector3(0, (float)(translationRatio * root.Bounds.Height), 0) :
- new Vector3((float)(translationRatio * root.Bounds.Width), 0, 0);
+ contentVisual.Offset += IsPullDirectionVertical ? new Vector3D(0, (translationRatio * root.Bounds.Height), 0) :
+ new Vector3D((translationRatio * root.Bounds.Width), 0, 0);
visualizerVisual.Offset = IsPullDirectionVertical ?
- new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
- new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
+ new Vector3D(visualizerVisual.Offset.X, offset.Y, 0) :
+ new Vector3D(offset.X, visualizerVisual.Offset.Y, 0);
break;
case RefreshVisualizerState.Peeking:
contentVisual.Opacity = 1;
@@ -452,8 +451,6 @@ namespace Avalonia.Controls
_interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty)
.Subscribe(InteractionRatioObserver);
- var visual = RefreshInfoProvider.Visual;
-
_executingRatio = RefreshInfoProvider.ExecutionRatio;
}
else
diff --git a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
index 4c1e0c2565..7ff02711d6 100644
--- a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
+++ b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
@@ -98,14 +98,14 @@ namespace Avalonia.Controls.PullToRefresh
if (_scrollViewer.Content == null)
{
- throw new ArgumentException(nameof(adaptee), "Adaptee's content property cannot be null.");
+ throw new ArgumentException("Adaptee's content property cannot be null.", nameof(adaptee));
}
var content = adaptee.Content as Visual;
if (content == null)
{
- throw new ArgumentException(nameof(adaptee), "Adaptee's content property must be a Visual");
+ throw new ArgumentException("Adaptee's content property must be a Visual", nameof(adaptee));
}
if (content.GetVisualParent() == null)
@@ -118,7 +118,7 @@ namespace Avalonia.Controls.PullToRefresh
if (content.Parent is not InputElement)
{
- throw new ArgumentException(nameof(adaptee), "Adaptee's content's parent must be a InputElement");
+ throw new ArgumentException("Adaptee's content's parent must be a InputElement", nameof(adaptee));
}
}
@@ -194,12 +194,12 @@ namespace Avalonia.Controls.PullToRefresh
var content = _scrollViewer?.Content as Visual;
if (content == null)
{
- throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual");
+ throw new ArgumentException("Adaptee's content property must be a Visual", nameof(_scrollViewer));
}
if (content.Parent is not InputElement parent)
{
- throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement");
+ throw new ArgumentException("Adaptee's content parent must be an InputElement", nameof(_scrollViewer));
}
MakeInteractionSource(parent);
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs
new file mode 100644
index 0000000000..593adfb225
--- /dev/null
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Avalonia.Platform;
+using Avalonia.Remote.Protocol.Viewport;
+using PlatformPixelFormat = Avalonia.Platform.PixelFormat;
+using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
+
+namespace Avalonia.Controls.Remote.Server
+{
+ internal partial class RemoteServerTopLevelImpl
+ {
+ private enum FrameStatus
+ {
+ NotRendered,
+ Rendered,
+ CopiedToMessage
+ }
+
+ private sealed class Framebuffer
+ {
+ public static Framebuffer Empty { get; } = new(ProtocolPixelFormat.Rgba8888, default, 1.0);
+
+ private readonly double _dpi;
+ private readonly PixelSize _frameSize;
+ private readonly object _dataLock = new();
+ private readonly byte[] _data; // for rendering only
+ private readonly byte[] _dataCopy; // for messages only
+ private FrameStatus _status = FrameStatus.NotRendered;
+
+ public Framebuffer(ProtocolPixelFormat format, Size clientSize, double renderScaling)
+ {
+ var frameSize = PixelSize.FromSize(clientSize, renderScaling);
+ if (frameSize.Width <= 0 || frameSize.Height <= 0)
+ frameSize = PixelSize.Empty;
+
+ var bpp = format == ProtocolPixelFormat.Rgb565 ? 2 : 4;
+ var stride = frameSize.Width * bpp;
+ var dataLength = Math.Max(0, stride * frameSize.Height);
+
+ _dpi = renderScaling * 96.0;
+ _frameSize = frameSize;
+ Format = format;
+ ClientSize = clientSize;
+ RenderScaling = renderScaling;
+
+ (Stride, _data, _dataCopy) = dataLength > 0 ?
+ (stride, new byte[dataLength], new byte[dataLength]) :
+ (0, Array.Empty(), Array.Empty());
+ }
+
+ public ProtocolPixelFormat Format { get; }
+
+ public Size ClientSize { get; }
+
+ public double RenderScaling { get; }
+
+ public int Stride { get; }
+
+ public FrameStatus GetStatus()
+ {
+ lock (_dataLock)
+ return _status;
+ }
+
+ public ILockedFramebuffer Lock(Action onUnlocked)
+ {
+ var handle = GCHandle.Alloc(_data, GCHandleType.Pinned);
+ Monitor.Enter(_dataLock);
+
+ try
+ {
+ return new LockedFramebuffer(
+ handle.AddrOfPinnedObject(),
+ _frameSize,
+ Stride,
+ new Vector(_dpi, _dpi),
+ new PlatformPixelFormat((PixelFormatEnum)Format),
+ () =>
+ {
+ handle.Free();
+ Array.Copy(_data, _dataCopy, _data.Length);
+ _status = FrameStatus.Rendered;
+ Monitor.Exit(_dataLock);
+ onUnlocked();
+ });
+ }
+ catch
+ {
+ handle.Free();
+ Monitor.Exit(_dataLock);
+ throw;
+ }
+ }
+
+ /// The returned message must NOT be kept around, as it contains a shared buffer.
+ public FrameMessage ToMessage(long sequenceId)
+ {
+ lock (_dataLock)
+ _status = FrameStatus.CopiedToMessage;
+
+ return new FrameMessage
+ {
+ SequenceId = sequenceId,
+ Data = _dataCopy,
+ Format = Format,
+ Width = _frameSize.Width,
+ Height = _frameSize.Height,
+ Stride = Stride,
+ DpiX = _dpi,
+ DpiY = _dpi
+ };
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index 8e4123a790..ac7e8ec179 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Runtime.InteropServices;
using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
@@ -11,7 +10,6 @@ using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
-using Avalonia.Rendering;
using Avalonia.Threading;
using Key = Avalonia.Input.Key;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
@@ -20,28 +18,28 @@ using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton;
namespace Avalonia.Controls.Remote.Server
{
[Unstable]
- internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl
+ internal partial class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl
{
private readonly IAvaloniaRemoteTransportConnection _transport;
- private LockedFramebuffer? _framebuffer;
private readonly object _lock = new();
+ private readonly Action _sendLastFrameIfNeeded;
+ private readonly Action _renderAndSendFrameIfNeeded;
+ private Framebuffer _framebuffer = Framebuffer.Empty;
private long _lastSentFrame = -1;
private long _lastReceivedFrame = -1;
private long _nextFrameNumber = 1;
private ClientViewportAllocatedMessage? _pendingAllocation;
- private bool _queuedNextRender;
- private bool _inRender;
- private Vector _dpi = new Vector(96, 96);
- private ProtocolPixelFormat[]? _supportedFormats;
+ private ProtocolPixelFormat? _format;
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport)
{
+ _sendLastFrameIfNeeded = SendLastFrameIfNeeded;
+ _renderAndSendFrameIfNeeded = RenderAndSendFrameIfNeeded;
+
_transport = transport;
_transport.OnMessage += OnMessage;
KeyboardDevice = AvaloniaLocator.Current.GetRequiredService();
- QueueNextRender();
- Compositor.AfterCommit += QueueNextRender;
}
private static RawPointerEventType GetAvaloniaEventType(ProtocolMouseButton button, bool pressed)
@@ -112,45 +110,41 @@ namespace Avalonia.Controls.Remote.Server
{
lock (_lock)
{
- if (obj is FrameReceivedMessage lastFrame)
+ switch (obj)
{
- lock (_lock)
- {
+ case FrameReceivedMessage lastFrame:
_lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame);
- }
- Dispatcher.UIThread.Post(RenderIfNeeded);
- }
- if(obj is ClientRenderInfoMessage renderInfo)
- {
- lock(_lock)
- {
- _dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY);
- _queuedNextRender = true;
- }
-
- Dispatcher.UIThread.Post(RenderIfNeeded);
- }
- if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
- {
- lock (_lock)
- _supportedFormats = supportedFormats.Formats;
- Dispatcher.UIThread.Post(RenderIfNeeded);
- }
- if (obj is MeasureViewportMessage measure)
- Dispatcher.UIThread.Post(() =>
- {
- var m = Measure(new Size(measure.Width, measure.Height));
- _transport.Send(new MeasureViewportMessage
+ Dispatcher.UIThread.Post(_sendLastFrameIfNeeded);
+ break;
+
+ case ClientRenderInfoMessage renderInfo:
+ Dispatcher.UIThread.Post(() =>
{
- Width = m.Width,
- Height = m.Height
+ RenderScaling = renderInfo.DpiX / 96.0;
+ RenderAndSendFrameIfNeeded();
});
- });
- if (obj is ClientViewportAllocatedMessage allocated)
- {
- lock (_lock)
- {
+ break;
+
+ case ClientSupportedPixelFormatsMessage supportedFormats:
+ _format = TryGetValidPixelFormat(supportedFormats.Formats);
+ Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded);
+ break;
+
+ case MeasureViewportMessage measure:
+ Dispatcher.UIThread.Post(() =>
+ {
+ var m = Measure(new Size(measure.Width, measure.Height));
+ _transport.Send(new MeasureViewportMessage
+ {
+ Width = m.Width,
+ Height = m.Height
+ });
+ });
+ break;
+
+ case ClientViewportAllocatedMessage allocated:
if (_pendingAllocation == null)
+ {
Dispatcher.UIThread.Post(() =>
{
ClientViewportAllocatedMessage allocation;
@@ -159,101 +153,111 @@ namespace Avalonia.Controls.Remote.Server
allocation = _pendingAllocation!;
_pendingAllocation = null;
}
- _dpi = new Vector(allocation.DpiX, allocation.DpiY);
+
+ RenderScaling = allocation.DpiX / 96.0;
ClientSize = new Size(allocation.Width, allocation.Height);
- RenderIfNeeded();
+ RenderAndSendFrameIfNeeded();
});
+ }
_pendingAllocation = allocated;
- }
- }
- if(obj is PointerMovedEventMessage pointer)
- {
- Dispatcher.UIThread.Post(() =>
- {
- Input?.Invoke(new RawPointerEventArgs(
- MouseDevice,
- 0,
- InputRoot!,
- RawPointerEventType.Move,
- new Point(pointer.X, pointer.Y),
- GetAvaloniaRawInputModifiers(pointer.Modifiers)));
- }, DispatcherPriority.Input);
- }
- if(obj is PointerPressedEventMessage pressed)
- {
- Dispatcher.UIThread.Post(() =>
- {
- Input?.Invoke(new RawPointerEventArgs(
- MouseDevice,
- 0,
- InputRoot!,
- GetAvaloniaEventType(pressed.Button, true),
- new Point(pressed.X, pressed.Y),
- GetAvaloniaRawInputModifiers(pressed.Modifiers)));
- }, DispatcherPriority.Input);
- }
- if (obj is PointerReleasedEventMessage released)
- {
- Dispatcher.UIThread.Post(() =>
- {
- Input?.Invoke(new RawPointerEventArgs(
- MouseDevice,
- 0,
- InputRoot!,
- GetAvaloniaEventType(released.Button, false),
- new Point(released.X, released.Y),
- GetAvaloniaRawInputModifiers(released.Modifiers)));
- }, DispatcherPriority.Input);
- }
- if(obj is ScrollEventMessage scroll)
- {
- Dispatcher.UIThread.Post(() =>
- {
- Input?.Invoke(new RawMouseWheelEventArgs(
- MouseDevice,
- 0,
- InputRoot!,
- new Point(scroll.X, scroll.Y),
- new Vector(scroll.DeltaX, scroll.DeltaY),
- GetAvaloniaRawInputModifiers(scroll.Modifiers)));
- }, DispatcherPriority.Input);
- }
- if(obj is KeyEventMessage key)
- {
- Dispatcher.UIThread.Post(() =>
- {
- Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
-
- Input?.Invoke(new RawKeyEventArgs(
- KeyboardDevice,
- 0,
- InputRoot!,
- key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
- (Key)key.Key,
- GetAvaloniaRawInputModifiers(key.Modifiers)));
- }, DispatcherPriority.Input);
- }
- if(obj is TextInputEventMessage text)
- {
- Dispatcher.UIThread.Post(() =>
- {
- Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
-
- Input?.Invoke(new RawTextInputEventArgs(
- KeyboardDevice,
- 0,
- InputRoot!,
- text.Text));
- }, DispatcherPriority.Input);
+ break;
+
+ case PointerMovedEventMessage pointer:
+ Dispatcher.UIThread.Post(() =>
+ {
+ Input?.Invoke(new RawPointerEventArgs(
+ MouseDevice,
+ 0,
+ InputRoot!,
+ RawPointerEventType.Move,
+ new Point(pointer.X, pointer.Y),
+ GetAvaloniaRawInputModifiers(pointer.Modifiers)));
+ }, DispatcherPriority.Input);
+ break;
+
+ case PointerPressedEventMessage pressed:
+ Dispatcher.UIThread.Post(() =>
+ {
+ Input?.Invoke(new RawPointerEventArgs(
+ MouseDevice,
+ 0,
+ InputRoot!,
+ GetAvaloniaEventType(pressed.Button, true),
+ new Point(pressed.X, pressed.Y),
+ GetAvaloniaRawInputModifiers(pressed.Modifiers)));
+ }, DispatcherPriority.Input);
+ break;
+
+ case PointerReleasedEventMessage released:
+ Dispatcher.UIThread.Post(() =>
+ {
+ Input?.Invoke(new RawPointerEventArgs(
+ MouseDevice,
+ 0,
+ InputRoot!,
+ GetAvaloniaEventType(released.Button, false),
+ new Point(released.X, released.Y),
+ GetAvaloniaRawInputModifiers(released.Modifiers)));
+ }, DispatcherPriority.Input);
+ break;
+
+ case ScrollEventMessage scroll:
+ Dispatcher.UIThread.Post(() =>
+ {
+ Input?.Invoke(new RawMouseWheelEventArgs(
+ MouseDevice,
+ 0,
+ InputRoot!,
+ new Point(scroll.X, scroll.Y),
+ new Vector(scroll.DeltaX, scroll.DeltaY),
+ GetAvaloniaRawInputModifiers(scroll.Modifiers)));
+ }, DispatcherPriority.Input);
+ break;
+
+ case KeyEventMessage key:
+ Dispatcher.UIThread.Post(() =>
+ {
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
+
+ Input?.Invoke(new RawKeyEventArgs(
+ KeyboardDevice,
+ 0,
+ InputRoot!,
+ key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
+ (Key)key.Key,
+ GetAvaloniaRawInputModifiers(key.Modifiers)));
+ }, DispatcherPriority.Input);
+ break;
+
+ case TextInputEventMessage text:
+ Dispatcher.UIThread.Post(() =>
+ {
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
+
+ Input?.Invoke(new RawTextInputEventArgs(
+ KeyboardDevice,
+ 0,
+ InputRoot!,
+ text.Text));
+ }, DispatcherPriority.Input);
+ break;
}
}
}
- protected void SetDpi(Vector dpi)
+ private static ProtocolPixelFormat? TryGetValidPixelFormat(ProtocolPixelFormat[]? formats)
{
- _dpi = dpi;
- RenderIfNeeded();
+ if (formats is not null)
+ {
+ foreach (var format in formats)
+ {
+ if (format is >= 0 and <= ProtocolPixelFormat.MaxValue)
+ return format;
+ }
+ }
+
+ return null;
}
protected virtual Size Measure(Size constraint)
@@ -265,92 +269,64 @@ namespace Avalonia.Controls.Remote.Server
public override IEnumerable Surfaces => new[] { this };
- private FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
+ private Framebuffer GetOrCreateFramebuffer()
{
- var scalingX = _dpi.X / 96.0;
- var scalingY = _dpi.Y / 96.0;
-
- width = (int)(width * scalingX);
- height = (int)(height * scalingY);
-
- var fmt = format ?? ProtocolPixelFormat.Rgba8888;
- var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
- var data = new byte[width * height * bpp];
- var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
-
- try
- {
- if (width > 0 && height > 0)
- {
- _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, new((PixelFormatEnum)fmt),
- null);
- Paint?.Invoke(new Rect(0, 0, width, height));
- }
- }
- finally
+ lock (_lock)
{
- _framebuffer = null;
- handle.Free();
+ if (_format is not { } format)
+ _framebuffer = Framebuffer.Empty;
+ else if (_framebuffer.Format != format || _framebuffer.ClientSize != ClientSize || _framebuffer.RenderScaling != RenderScaling)
+ _framebuffer = new Framebuffer(format, ClientSize, RenderScaling);
+
+ return _framebuffer;
}
- return new FrameMessage
- {
- Data = data,
- Format = fmt,
- Width = width,
- Height = height,
- Stride = width * bpp,
- DpiX = _dpi.X,
- DpiY = _dpi.Y
- };
}
- public ILockedFramebuffer Lock()
+ private void SendLastFrameIfNeeded()
{
- if (_framebuffer == null)
- throw new InvalidOperationException("Paint was not requested, wait for Paint event");
- return _framebuffer;
- }
+ if (IsDisposed)
+ return;
+
+ Framebuffer framebuffer;
+ long sequenceId;
- protected void RenderIfNeeded()
- {
lock (_lock)
{
- if (_lastReceivedFrame != _lastSentFrame || !_queuedNextRender || _supportedFormats == null)
+ if (_lastReceivedFrame != _lastSentFrame || _framebuffer.GetStatus() != FrameStatus.Rendered)
return;
- }
-
- var format = ProtocolPixelFormat.Rgba8888;
- foreach(var fmt in _supportedFormats)
- if (fmt <= ProtocolPixelFormat.MaxValue)
- {
- format = fmt;
- break;
- }
-
- _inRender = true;
- var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format);
- lock (_lock)
- {
+ framebuffer = _framebuffer;
_lastSentFrame = _nextFrameNumber++;
- frame.SequenceId = _lastSentFrame;
- _queuedNextRender = false;
+ sequenceId = _lastSentFrame;
}
- _inRender = false;
- _transport.Send(frame);
+
+ _transport.Send(framebuffer.ToMessage(sequenceId));
}
- private void QueueNextRender()
+ protected void RenderAndSendFrameIfNeeded()
{
- if (!_inRender && !IsDisposed)
+ if (IsDisposed)
+ return;
+
+ lock (_lock)
{
- _queuedNextRender = true;
- DispatcherTimer.RunOnce(RenderIfNeeded, TimeSpan.FromMilliseconds(2), DispatcherPriority.Background);
+ if (_lastReceivedFrame != _lastSentFrame || _format is null)
+ return;
}
+
+ var framebuffer = GetOrCreateFramebuffer();
+
+ if (framebuffer.Stride > 0)
+ Paint?.Invoke(new Rect(framebuffer.ClientSize));
+
+ SendLastFrameIfNeeded();
}
public override IMouseDevice MouseDevice { get; } = new MouseDevice();
public IKeyboardDevice KeyboardDevice { get; }
+
+ public IFramebufferRenderTarget CreateFramebufferRenderTarget() =>
+ new FuncFramebufferRenderTarget(() => GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded));
}
}
diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs
index 8b32638835..5e21681024 100644
--- a/src/Avalonia.Controls/SelectableTextBlock.cs
+++ b/src/Avalonia.Controls/SelectableTextBlock.cs
@@ -198,7 +198,7 @@ namespace Avalonia.Controls
var handled = false;
var modifiers = e.KeyModifiers;
- var keymap = AvaloniaLocator.Current.GetRequiredService();
+ var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index 68bad598d0..69bed2550e 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -277,7 +277,7 @@ namespace Avalonia.Controls.Selection
{
if (base.Source != value)
{
- if (_operation is not null)
+ if (_operation?.UpdateCount > 0)
{
throw new InvalidOperationException("Cannot change source while update is in progress.");
}
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 57d709ba94..ea420c7c45 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -668,17 +668,7 @@ namespace Avalonia.Controls
if (HasComplexContent)
{
- if (_textRuns != null)
- {
- foreach (var textRun in _textRuns)
- {
- if (textRun is EmbeddedControlRun controlRun &&
- controlRun.Control is Control control)
- {
- VisualChildren.Remove(control);
- }
- }
- }
+ VisualChildren.Clear();
var textRuns = new List();
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 7bc26bf3b0..98b3b13c17 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -15,7 +15,6 @@ using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Controls.Metadata;
using Avalonia.Media.TextFormatting;
-using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers;
using Avalonia.Threading;
@@ -32,20 +31,17 @@ namespace Avalonia.Controls
///
/// Gets a platform-specific for the Cut action
///
- public static KeyGesture? CutGesture { get; } = AvaloniaLocator.Current
- .GetService()?.Cut.FirstOrDefault();
+ public static KeyGesture? CutGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Cut.FirstOrDefault();
///
/// Gets a platform-specific for the Copy action
///
- public static KeyGesture? CopyGesture { get; } = AvaloniaLocator.Current
- .GetService()?.Copy.FirstOrDefault();
+ public static KeyGesture? CopyGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Copy.FirstOrDefault();
///
/// Gets a platform-specific for the Paste action
///
- public static KeyGesture? PasteGesture { get; } = AvaloniaLocator.Current
- .GetService()?.Paste.FirstOrDefault();
+ public static KeyGesture? PasteGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Paste.FirstOrDefault();
///
/// Defines the property
@@ -816,9 +812,14 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- if (IsFocused)
+ if (_presenter != null)
{
- _presenter?.ShowCaret();
+ if (IsFocused)
+ {
+ _presenter.ShowCaret();
+ }
+
+ _presenter.PropertyChanged += PresenterPropertyChanged;
}
}
@@ -826,9 +827,29 @@ namespace Avalonia.Controls
{
base.OnDetachedFromVisualTree(e);
+ if (_presenter != null)
+ {
+ _presenter.HideCaret();
+
+ _presenter.PropertyChanged -= PresenterPropertyChanged;
+ }
+
_imClient.SetPresenter(null, null);
}
+ private void PresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if(e.Property == TextPresenter.PreeditTextProperty)
+ {
+ if(string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string))
+ {
+ PseudoClasses.Set(":empty", false);
+
+ DeleteSelection();
+ }
+ }
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -979,7 +1000,9 @@ namespace Avalonia.Controls
textBuilder.Insert(caretIndex, input);
- SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(textBuilder));
+ var text = StringBuilderCache.GetStringAndRelease(textBuilder);
+
+ SetCurrentValue(TextProperty, text);
ClearSelection();
@@ -1103,7 +1126,7 @@ namespace Avalonia.Controls
var handled = false;
var modifiers = e.KeyModifiers;
- var keymap = AvaloniaLocator.Current.GetRequiredService();
+ var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers);
@@ -1217,6 +1240,34 @@ namespace Avalonia.Controls
selection = true;
handled = true;
}
+ else if (Match(keymap.PageLeft))
+ {
+ MovePageLeft();
+ movement = true;
+ selection = false;
+ handled = true;
+ }
+ else if (Match(keymap.PageRight))
+ {
+ MovePageRight();
+ movement = true;
+ selection = false;
+ handled = true;
+ }
+ else if (Match(keymap.PageUp))
+ {
+ MovePageUp();
+ movement = true;
+ selection = false;
+ handled = true;
+ }
+ else if (Match(keymap.PageDown))
+ {
+ MovePageDown();
+ movement = true;
+ selection = false;
+ handled = true;
+ }
else
{
bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers);
@@ -1407,8 +1458,6 @@ namespace Avalonia.Controls
{
var point = e.GetPosition(_presenter);
- var oldIndex = CaretIndex;
-
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
@@ -1741,6 +1790,25 @@ namespace Avalonia.Controls
}
}
+ private void MovePageRight()
+ {
+ _scrollViewer?.PageRight();
+ }
+
+ private void MovePageLeft()
+ {
+ _scrollViewer?.PageLeft();
+ }
+ private void MovePageUp()
+ {
+ _scrollViewer?.PageUp();
+ }
+
+ private void MovePageDown()
+ {
+ _scrollViewer?.PageDown();
+ }
+
///
/// Select all text in the TextBox
///
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index e999d0bb74..28a230640a 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -1,243 +1,114 @@
using System;
-using System.Diagnostics;
using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
-using Avalonia.Media;
using Avalonia.Media.TextFormatting;
-using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
- internal class TextBoxTextInputMethodClient : ITextInputMethodClient
+ internal class TextBoxTextInputMethodClient : TextInputMethodClient
{
private TextBox? _parent;
private TextPresenter? _presenter;
- private ITextEditable? _textEditable;
- public Visual TextViewVisual => _presenter!;
+ public override Visual TextViewVisual => _presenter!;
- public bool SupportsPreedit => true;
-
- public bool SupportsSurroundingText => true;
-
- public Rect CursorRectangle
+ public override string SurroundingText
{
get
{
- if (_parent == null || _presenter == null)
+ if (_presenter is null || _parent is null)
{
- return default;
+ return "";
}
-
- var transform = _presenter.TransformToVisual(_parent);
-
- if (transform == null)
+
+ if (_parent.CaretIndex != _presenter.CaretIndex)
{
- return default;
+ _presenter.SetCurrentValue(TextPresenter.CaretIndexProperty, _parent.CaretIndex);
}
- var rect = _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
-
- return rect;
- }
- }
-
- public TextInputMethodSurroundingText SurroundingText
- {
- get
- {
- if (_presenter is null || _parent is null)
+ if (_parent.Text != _presenter.Text)
{
- return default;
+ _presenter.SetCurrentValue(TextPresenter.TextProperty, _parent.Text);
}
-
+
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
var textLine = _presenter.TextLayout.TextLines[lineIndex];
- var lineStart = textLine.FirstTextSourceIndex;
-
var lineText = GetTextLineText(textLine);
- var anchorOffset = Math.Max(0, _parent.SelectionStart - lineStart);
-
- var cursorOffset = Math.Max(0, _presenter.SelectionEnd - lineStart);
-
- return new TextInputMethodSurroundingText
- {
- Text = lineText ?? "",
- AnchorOffset = anchorOffset,
- CursorOffset = cursorOffset
- };
+ return lineText;
}
}
- public ITextEditable? TextEditable
+ public override Rect CursorRectangle
{
- get => _textEditable; set
+ get
{
- if (_textEditable != null)
+ if (_parent == null || _presenter == null)
{
- _textEditable.TextChanged -= TextEditable_TextChanged;
- _textEditable.SelectionChanged -= TextEditable_SelectionChanged;
- _textEditable.CompositionChanged -= TextEditable_CompositionChanged;
+ return default;
}
- _textEditable = value;
+ var transform = _presenter.TransformToVisual(_parent);
- if (_textEditable != null)
+ if (transform == null)
{
- _textEditable.TextChanged += TextEditable_TextChanged;
- _textEditable.SelectionChanged += TextEditable_SelectionChanged;
- _textEditable.CompositionChanged += TextEditable_CompositionChanged;
-
- if (_presenter != null)
- {
- _textEditable.Text = _presenter.Text;
- _textEditable.SelectionStart = _presenter.SelectionStart;
- _textEditable.SelectionEnd = _presenter.SelectionEnd;
- }
+ return default;
}
- }
- }
- private void TextEditable_CompositionChanged(object? sender, EventArgs e)
- {
- if (_presenter != null && _textEditable != null)
- {
- _presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd));
+ return _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
}
}
- private void TextEditable_SelectionChanged(object? sender, EventArgs e)
+ public override TextSelection Selection
{
- if (_parent != null && _textEditable != null)
- {
- _parent.SelectionStart = _textEditable.SelectionStart;
- _parent.SelectionEnd = _textEditable.SelectionEnd;
- }
- }
-
- private void TextEditable_TextChanged(object? sender, EventArgs e)
- {
- if (_parent != null)
- {
- if (_parent.Text != _textEditable?.Text)
- {
- _parent.Text = _textEditable?.Text;
- }
- }
- }
-
- private static string GetTextLineText(TextLine textLine)
- {
- var builder = StringBuilderCache.Acquire(textLine.Length);
-
- foreach (var run in textLine.TextRuns)
+ get
{
- if (run.Length > 0)
+ if (_presenter is null || _parent is null)
{
-#if NET6_0_OR_GREATER
- builder.Append(run.Text.Span);
-#else
- builder.Append(run.Text.Span.ToArray());
-#endif
+ return default;
}
- }
-
- var lineText = builder.ToString();
- StringBuilderCache.Release(builder);
-
- return lineText;
- }
-
- public event EventHandler? TextViewVisualChanged;
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_parent.CaretIndex, false);
- public event EventHandler? CursorRectangleChanged;
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
- public event EventHandler? SurroundingTextChanged;
+ var lineStart = textLine.FirstTextSourceIndex;
- private string? _presenterText;
- private int _compositionStart;
+ var selectionStart = Math.Max(0, _parent.SelectionStart - lineStart);
- public void SetPreeditText(string? preeditText)
- {
- if (_presenter == null || _parent == null)
- {
- return;
- }
+ var selectionEnd = Math.Max(0, _parent.SelectionEnd - lineStart);
- if (_presenterText is null)
- {
- _presenterText = _parent.Text ?? "";
- _compositionStart = _parent.CaretIndex;
+ return new TextSelection(selectionStart, selectionEnd);
}
-
- var text = GetText(preeditText);
-
- _presenter.SetCurrentValue(TextPresenter.TextProperty, text);
-
- _presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
-
- _presenter.UpdateCaret(new CharacterHit(_compositionStart + (preeditText != null ? preeditText.Length : 0)), false);
-
- if (string.IsNullOrEmpty(preeditText))
+ set
{
- _presenterText = null;
- }
- }
+ if (_parent is null || _presenter is null)
+ {
+ return;
+ }
- private string? GetText(string? preeditText)
- {
- if (string.IsNullOrEmpty(preeditText))
- {
- return _presenterText;
- }
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_parent.CaretIndex, false);
- if (string.IsNullOrEmpty(_presenterText))
- {
- return preeditText;
- }
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
- var sb = StringBuilderCache.Acquire(_presenterText.Length + preeditText.Length);
+ var lineStart = textLine.FirstTextSourceIndex;
- sb.Append(_presenterText);
- sb.Insert(_compositionStart, preeditText);
+ var selectionStart = lineStart + value.Start;
+ var selectionEnd = lineStart + value.End;
- return StringBuilderCache.GetStringAndRelease(sb);
- }
+ _parent.SelectionStart = selectionStart;
+ _parent.SelectionEnd = selectionEnd;
- public void SetComposingRegion(TextRange? region)
- {
- if (_presenter == null)
- {
- return;
+ RaiseSelectionChanged();
}
-
- _presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, region);
}
- public void SelectInSurroundingText(int start, int end)
- {
- if (_parent is null || _presenter is null)
- {
- return;
- }
-
- var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
-
- var textLine = _presenter.TextLayout.TextLines[lineIndex];
-
- var lineStart = textLine.FirstTextSourceIndex;
+ public override bool SupportsPreedit => true;
- var selectionStart = lineStart + start;
- var selectionEnd = lineStart + end;
-
- _parent.SelectionStart = selectionStart;
- _parent.SelectionEnd = selectionEnd;
- }
+ public override bool SupportsSurroundingText => true;
public void SetPresenter(TextPresenter? presenter, TextBox? parent)
{
@@ -253,66 +124,71 @@ namespace Avalonia.Controls
_parent.PropertyChanged += OnParentPropertyChanged;
}
- if (_presenter != null)
- {
- _presenter.ClearValue(TextPresenter.PreeditTextProperty);
+ var oldPresenter = _presenter;
- _presenter.ClearValue(TextPresenter.CompositionRegionProperty);
+ if (oldPresenter != null)
+ {
+ oldPresenter.ClearValue(TextPresenter.PreeditTextProperty);
- _presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
+ oldPresenter.CaretBoundsChanged -= (s,e) => RaiseCursorRectangleChanged();
}
_presenter = presenter;
if (_presenter != null)
{
- _presenter.CaretBoundsChanged += OnCaretBoundsChanged;
+ _presenter.CaretBoundsChanged += (s, e) => RaiseCursorRectangleChanged();
}
- TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
+ RaiseTextViewVisualChanged();
- OnCaretBoundsChanged(this, EventArgs.Empty);
+ RaiseCursorRectangleChanged();
}
- private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ public override void SetPreeditText(string? preeditText)
{
- if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
+ if (_presenter == null || _parent == null)
{
- if (SupportsSurroundingText)
- {
- SurroundingTextChanged?.Invoke(this, e);
- }
- if (_textEditable != null)
- {
- var value = (int)(e.NewValue ?? 0);
- if (e.Property == TextBox.SelectionStartProperty)
- {
- _textEditable.SelectionStart = value;
- }
-
- if (e.Property == TextBox.SelectionEndProperty)
- {
- _textEditable.SelectionEnd = value;
- }
- }
+ return;
}
- if (e.Property == TextBox.TextProperty)
+ _presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
+ }
+
+ private static string GetTextLineText(TextLine textLine)
+ {
+ var builder = StringBuilderCache.Acquire(textLine.Length);
+
+ foreach (var run in textLine.TextRuns)
{
- if (_textEditable != null)
+ if (run.Length > 0)
{
- _textEditable.Text = (string?)e.NewValue;
+#if NET6_0_OR_GREATER
+ builder.Append(run.Text.Span);
+#else
+ builder.Append(run.Text.Span.ToArray());
+#endif
}
}
+
+ var lineText = builder.ToString();
+
+ StringBuilderCache.Release(builder);
+
+ return lineText;
}
- private void OnCaretBoundsChanged(object? sender, EventArgs e)
+ private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
- Dispatcher.UIThread.Post(() =>
+ if (e.Property == TextBox.TextProperty)
{
- CursorRectangleChanged?.Invoke(this, e);
+ RaiseSurroundingTextChanged();
+ }
- }, DispatcherPriority.Input);
+ if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
+ {
+ RaiseSelectionChanged();
+ }
}
}
}
diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs
index a68a022e67..dc6b946381 100644
--- a/src/Avalonia.Controls/ToggleSwitch.cs
+++ b/src/Avalonia.Controls/ToggleSwitch.cs
@@ -1,7 +1,9 @@
-using Avalonia.Controls.Metadata;
+using Avalonia.Animation;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
+using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
@@ -42,6 +44,10 @@ namespace Avalonia.Controls
x.UpdateKnobPos(x.IsChecked.Value);
}
});
+ KnobTransitionsProperty.Changed.AddClassHandler((x, e) =>
+ {
+ x.UpdateKnobTransitions();
+ });
}
///
@@ -68,6 +74,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty OnContentTemplateProperty =
AvaloniaProperty.Register(nameof(OnContentTemplate));
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty KnobTransitionsProperty =
+ AvaloniaProperty.Register(nameof(KnobTransitions));
+
///
/// Gets or Sets the Content that is displayed when in the On State.
///
@@ -116,6 +128,17 @@ namespace Avalonia.Controls
set { SetValue(OnContentTemplateProperty, value); }
}
+ ///
+ /// Gets or Sets the of switching knob.
+ ///
+ public Transitions KnobTransitions
+ {
+ get { return GetValue(KnobTransitionsProperty); }
+ set { SetValue(KnobTransitionsProperty, value); }
+ }
+
+
+
private void OffContentChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)
@@ -177,7 +200,22 @@ namespace Avalonia.Controls
UpdateKnobPos(IsChecked.Value);
}
}
-
+
+ ///
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+ UpdateKnobTransitions();
+ }
+
+ private void UpdateKnobTransitions()
+ {
+ if (_knobsPanel != null)
+ {
+ _knobsPanel.Transitions = KnobTransitions;
+ }
+ }
+
private void KnobsPanel_PointerPressed(object? sender, Input.PointerPressedEventArgs e)
{
_switchStartPoint = e.GetPosition(_switchKnob);
@@ -194,7 +232,7 @@ namespace Avalonia.Controls
_knobsPanel!.ClearValue(Canvas.LeftProperty);
PseudoClasses.Set(":dragging", false);
-
+
if (shouldBecomeChecked == IsChecked)
{
UpdateKnobPos(shouldBecomeChecked);
@@ -203,6 +241,7 @@ namespace Avalonia.Controls
{
SetCurrentValue(IsCheckedProperty, shouldBecomeChecked);
}
+ UpdateKnobTransitions();
}
else
{
@@ -218,6 +257,10 @@ namespace Avalonia.Controls
{
if (_knobsPanelPressed)
{
+ if(_knobsPanel != null)
+ {
+ _knobsPanel.Transitions = null;
+ }
var difference = e.GetPosition(_switchKnob) - _switchStartPoint;
if ((!_isDragging) && (System.Math.Abs(difference.X) > 3))
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index ec6b3f7421..66e402d642 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -25,6 +25,7 @@ using System.Linq;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Rendering.Composition;
+using Avalonia.Threading;
namespace Avalonia.Controls
{
@@ -250,7 +251,7 @@ namespace Avalonia.Controls
if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown)
{
- var keymap = AvaloniaLocator.Current.GetService()?.Back;
+ var keymap = PlatformSettings?.HotkeyConfiguration.Back;
if (keymap != null)
{
@@ -391,7 +392,7 @@ namespace Avalonia.Controls
/// An describing the window handle, or null if the handle
/// could not be retrieved.
///
- public IPlatformHandle? TryGetPlatformHandle() => ((IWindowBaseImpl?) PlatformImpl)?.Handle;
+ public IPlatformHandle? TryGetPlatformHandle() => (PlatformImpl as IWindowBaseImpl)?.Handle;
///
/// Gets the renderer for the window.
@@ -487,6 +488,9 @@ namespace Avalonia.Controls
///
public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService();
+ ///
+ public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService();
+
///
Point IRenderRoot.PointToClient(PixelPoint p)
{
@@ -532,7 +536,16 @@ namespace Avalonia.Controls
return Disposable.Create(() => { });
}
}
-
+
+ ///
+ /// Enqueues a callback to be called on the next animation tick
+ ///
+ public void RequestAnimationFrame(Action action)
+ {
+ Dispatcher.UIThread.VerifyAccess();
+ MediaContext.Instance.RequestAnimationFrame(action);
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -578,6 +591,7 @@ namespace Avalonia.Controls
Renderer.SceneInvalidated -= SceneInvalidated;
// We need to wait for the renderer to complete any in-flight operations
Renderer.Dispose();
+ StopRendering();
Debug.Assert(PlatformImpl != null);
// The PlatformImpl is completely invalid at this point
diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs
index 21b9c9b765..0b04866d64 100644
--- a/src/Avalonia.Controls/TransitioningContentControl.cs
+++ b/src/Avalonia.Controls/TransitioningContentControl.cs
@@ -15,8 +15,9 @@ namespace Avalonia.Controls;
public class TransitioningContentControl : ContentControl
{
private CancellationTokenSource? _currentTransition;
- private ContentPresenter? _transitionPresenter;
- private Optional _transitionFrom;
+ private ContentPresenter? _presenter2;
+ private bool _isFirstFull;
+ private bool _shouldAnimate;
///
/// Defines the property.
@@ -39,46 +40,56 @@ public class TransitioningContentControl : ContentControl
{
var result = base.ArrangeOverride(finalSize);
- if (_transitionFrom.HasValue)
+ if (_shouldAnimate)
{
_currentTransition?.Cancel();
- if (_transitionPresenter is not null &&
+ if (_presenter2 is not null &&
Presenter is Visual presenter &&
- PageTransition is { } transition &&
- (_transitionFrom.Value is not Visual v || v.VisualParent is null))
- {
- _transitionPresenter.Content = _transitionFrom.Value;
- _transitionPresenter.IsVisible = true;
- _transitionFrom = Optional.Empty;
+ PageTransition is { } transition)
+ {
+ _shouldAnimate = false;
var cancel = new CancellationTokenSource();
_currentTransition = cancel;
- transition.Start(_transitionPresenter, presenter, true, cancel.Token).ContinueWith(x =>
+ var from = _isFirstFull ? _presenter2 : presenter;
+ var to = _isFirstFull ? presenter : _presenter2;
+
+ transition.Start(from, to, true, cancel.Token).ContinueWith(x =>
{
if (!cancel.IsCancellationRequested)
{
- _transitionPresenter.Content = null;
- _transitionPresenter.IsVisible = false;
+ HideOldPresenter();
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
- _transitionFrom = Optional.Empty;
+ _shouldAnimate = false;
}
return result;
}
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ UpdateContent(false);
+ }
+
protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
- if (!base.RegisterContentPresenter(presenter) &&
- presenter is ContentPresenter p &&
- p.Name == "PART_TransitionContentPresenter")
+ if (base.RegisterContentPresenter(presenter))
{
- _transitionPresenter = p;
- _transitionPresenter.IsVisible = false;
+ return true;
+ }
+
+ if (presenter is ContentPresenter p &&
+ p.Name == "PART_ContentPresenter2")
+ {
+ _presenter2 = p;
+ _presenter2.IsVisible = false;
+ UpdateContent(false);
return true;
}
@@ -87,16 +98,47 @@ public class TransitioningContentControl : ContentControl
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
+ if (change.Property == ContentProperty)
+ {
+ UpdateContent(true);
+ return;
+ }
+
base.OnPropertyChanged(change);
+ }
- if (change.Property == ContentProperty &&
- _transitionPresenter is not null &&
- Presenter is Visual &&
- PageTransition is not null)
+ private void UpdateContent(bool withTransition)
+ {
+ if (VisualRoot is null || _presenter2 is null || Presenter is null)
{
- _transitionFrom = change.GetOldValue();
+ return;
+ }
+
+ var currentPresenter = _isFirstFull ? _presenter2 : Presenter;
+ currentPresenter.Content = Content;
+ currentPresenter.IsVisible = true;
+
+ _isFirstFull = !_isFirstFull;
+
+ if (PageTransition is not null && withTransition)
+ {
+ _shouldAnimate = true;
InvalidateArrange();
}
+ else
+ {
+ HideOldPresenter();
+ }
+ }
+
+ private void HideOldPresenter()
+ {
+ var oldPresenter = _isFirstFull ? _presenter2 : Presenter;
+ if (oldPresenter is not null)
+ {
+ oldPresenter.Content = null;
+ oldPresenter.IsVisible = false;
+ }
}
private class ImmutableCrossFade : IPageTransition
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index a499829d4a..56a5999f29 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -84,6 +84,11 @@ namespace Avalonia.Controls
///
/// Gets or sets a value indicating whether to automatically scroll to newly selected items.
///
+ ///
+ /// This property is of limited use with as it will only scroll
+ /// to realized items. To scroll to a non-expanded item, you need to ensure that its
+ /// ancestors are expanded.
+ ///
public bool AutoScrollToSelectedItem
{
get => GetValue(AutoScrollToSelectedItemProperty);
@@ -353,9 +358,13 @@ namespace Avalonia.Controls
SelectedItemsAdded(e.NewItems!.Cast().ToArray());
- if (AutoScrollToSelectedItem)
+ var selectedItem = SelectedItem;
+
+ if (AutoScrollToSelectedItem &&
+ selectedItem is not null &&
+ e.NewItems![0] == selectedItem)
{
- var container = ContainerFromItem(e.NewItems![0]!);
+ var container = TreeContainerFromItem(selectedItem);
container?.BringIntoView();
}
@@ -531,6 +540,12 @@ namespace Avalonia.Controls
// The IsSelected property is not set on the container: update the container
// selection based on the current selection as understood by this control.
MarkContainerSelected(container, SelectedItems.Contains(item));
+
+ // If the newly realized container is the selected container, scroll to it after layout.
+ if (AutoScrollToSelectedItem && SelectedItem == item)
+ {
+ Dispatcher.UIThread.Post(container.BringIntoView, DispatcherPriority.Loaded);
+ }
}
///
@@ -571,7 +586,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
- var keymap = AvaloniaLocator.Current.GetService();
+ var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
bool Match(List? gestures) => gestures?.Any(g => g.Matches(e)) ?? false;
if (this.SelectionMode == SelectionMode.Multiple && Match(keymap?.SelectAll))
@@ -652,11 +667,12 @@ namespace Avalonia.Controls
if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed)
{
+ var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
- e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers),
+ e.KeyModifiers.HasAllFlags(keymap.CommandModifiers),
point.Properties.IsRightButtonPressed);
}
}
diff --git a/src/Avalonia.Controls/Utils/StringUtils.cs b/src/Avalonia.Controls/Utils/StringUtils.cs
index 2ea47b442a..0ae1ef0b97 100644
--- a/src/Avalonia.Controls/Utils/StringUtils.cs
+++ b/src/Avalonia.Controls/Utils/StringUtils.cs
@@ -76,7 +76,7 @@ namespace Avalonia.Controls.Utils
}
var codepoint = new Codepoint(text[index]);
-
+
if (!codepoint.IsWhiteSpace)
{
return false;
@@ -85,12 +85,20 @@ namespace Avalonia.Controls.Utils
// preceeded by lwsp.
if (index > 0)
{
- var nextCodePoint = new Codepoint(text[index + 1]);
+ if (index + 1 < text.Length)
+ {
+ var nextCodePoint = new Codepoint(text[index + 1]);
- if (nextCodePoint.IsBreakChar)
+ if (nextCodePoint.IsBreakChar)
+ {
+ return true;
+ }
+ }
+ else
{
return true;
}
+
}
switch (codepoint.GeneralCategory)
@@ -125,7 +133,7 @@ namespace Avalonia.Controls.Utils
}
cursor = Math.Min(cursor, text.Length);
-
+
int begin;
int i;
int cr;
@@ -181,7 +189,7 @@ namespace Avalonia.Controls.Utils
{
return cursor;
}
-
+
if (cr < text.Length && text[cr] == '\r' && cr + 1 < text.Length && text[cr + 1] == '\n')
{
lf = cr + 1;
@@ -214,9 +222,9 @@ namespace Avalonia.Controls.Utils
{
return i;
}
-
+
var cc = GetCharClass(text[i]);
-
+
// skip over the word, punctuation, or run of whitespace
while (i < cr && GetCharClass(text[i]) == cc)
{
diff --git a/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs b/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs
new file mode 100644
index 0000000000..eaf501b45f
--- /dev/null
+++ b/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Primitives;
+using Avalonia.Layout;
+
+namespace Avalonia.Controls.Utils
+{
+ internal class VirtualizingSnapPointsList : IReadOnlyList
+ {
+ private const int ExtraCount = 2;
+ private readonly RealizedStackElements _realizedElements;
+ private readonly Orientation _orientation;
+ private readonly Orientation _parentOrientation;
+ private readonly SnapPointsAlignment _snapPointsAlignment;
+ private readonly double _size;
+ private readonly int _start = -1;
+ private readonly int _end;
+
+ public VirtualizingSnapPointsList(RealizedStackElements realizedElements, int count, Orientation orientation, Orientation parentOrientation, SnapPointsAlignment snapPointsAlignment, double size)
+ {
+ _realizedElements = realizedElements;
+ _orientation = orientation;
+ _parentOrientation = parentOrientation;
+ _snapPointsAlignment = snapPointsAlignment;
+ _size = size;
+ if (parentOrientation == orientation)
+ {
+ _start = Math.Max(0, _realizedElements.FirstIndex - ExtraCount);
+ _end = Math.Min(count - 1, _realizedElements.LastIndex + ExtraCount);
+ }
+ }
+
+ public double this[int index]
+ {
+ get
+ {
+ if(index < 0 || index >= Count)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ index += _start;
+
+ double snapPoint = 0;
+ var averageElementSize = _size;
+
+ Control? container;
+ switch (_orientation)
+ {
+ case Orientation.Horizontal:
+ container = _realizedElements.GetElement(index);
+ if (container != null)
+ {
+ switch (_snapPointsAlignment)
+ {
+ case SnapPointsAlignment.Near:
+ snapPoint = container.Bounds.Left;
+ break;
+ case SnapPointsAlignment.Center:
+ snapPoint = container.Bounds.Center.X;
+ break;
+ case SnapPointsAlignment.Far:
+ snapPoint = container.Bounds.Right;
+ break;
+ }
+ }
+ else
+ {
+ var ind = index;
+ if (index > _realizedElements.LastIndex)
+ {
+ ind -= _realizedElements.LastIndex + 1;
+ }
+ snapPoint = ind * averageElementSize;
+ switch (_snapPointsAlignment)
+ {
+ case SnapPointsAlignment.Center:
+ snapPoint += averageElementSize / 2;
+ break;
+ case SnapPointsAlignment.Far:
+ snapPoint += averageElementSize;
+ break;
+ }
+ if (index > _realizedElements.LastIndex)
+ {
+ var lastElement = _realizedElements.GetElement(_realizedElements.LastIndex);
+ if (lastElement != null)
+ {
+ snapPoint += lastElement.Bounds.Right;
+ }
+ }
+ }
+ break;
+ case Orientation.Vertical:
+ container = _realizedElements.GetElement(index);
+ if (container != null)
+ {
+ switch (_snapPointsAlignment)
+ {
+ case SnapPointsAlignment.Near:
+ snapPoint = container.Bounds.Top;
+ break;
+ case SnapPointsAlignment.Center:
+ snapPoint = container.Bounds.Center.Y;
+ break;
+ case SnapPointsAlignment.Far:
+ snapPoint = container.Bounds.Bottom;
+ break;
+ }
+ }
+ else
+ {
+ var ind = index;
+ if(index > _realizedElements.LastIndex)
+ {
+ ind -= _realizedElements.LastIndex + 1;
+ }
+ snapPoint = ind * averageElementSize;
+ switch (_snapPointsAlignment)
+ {
+ case SnapPointsAlignment.Center:
+ snapPoint += averageElementSize / 2;
+ break;
+ case SnapPointsAlignment.Far:
+ snapPoint += averageElementSize;
+ break;
+ }
+ if (index > _realizedElements.LastIndex)
+ {
+ var lastElement = _realizedElements.GetElement(_realizedElements.LastIndex);
+ if (lastElement != null)
+ {
+ snapPoint += lastElement.Bounds.Bottom;
+ }
+ }
+ }
+ break;
+ }
+
+ return snapPoint;
+ }
+ }
+
+ public int Count => _parentOrientation != _orientation ? 0 : _end - _start + 1;
+
+ public IEnumerator GetEnumerator()
+ {
+ for (var i = 0; i < Count; i++)
+ yield return this[i];
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index b234686f5e..6a3c7e17ee 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -776,107 +776,10 @@ namespace Avalonia.Controls
///
public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
- var snapPoints = new List();
+ if(_realizedElements == null)
+ return new List();
- switch (orientation)
- {
- case Orientation.Horizontal:
- if (AreHorizontalSnapPointsRegular)
- throw new InvalidOperationException();
- if (Orientation == Orientation.Horizontal)
- {
- var averageElementSize = EstimateElementSizeU();
- double snapPoint = 0;
- for (var i = 0; i < Items.Count; i++)
- {
- var container = ContainerFromIndex(i);
- if (container != null)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Near:
- snapPoint = container.Bounds.Left;
- break;
- case SnapPointsAlignment.Center:
- snapPoint = container.Bounds.Center.X;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = container.Bounds.Right;
- break;
- }
- }
- else
- {
- if (snapPoint == 0)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Center:
- snapPoint = averageElementSize / 2;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = averageElementSize;
- break;
- }
- }
- else
- snapPoint += averageElementSize;
- }
-
- snapPoints.Add(snapPoint);
- }
- }
- break;
- case Orientation.Vertical:
- if (AreVerticalSnapPointsRegular)
- throw new InvalidOperationException();
- if (Orientation == Orientation.Vertical)
- {
- var averageElementSize = EstimateElementSizeU();
- double snapPoint = 0;
- for (var i = 0; i < Items.Count; i++)
- {
- var container = ContainerFromIndex(i);
- if (container != null)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Near:
- snapPoint = container.Bounds.Top;
- break;
- case SnapPointsAlignment.Center:
- snapPoint = container.Bounds.Center.Y;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = container.Bounds.Bottom;
- break;
- }
- }
- else
- {
- if (snapPoint == 0)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Center:
- snapPoint = averageElementSize / 2;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = averageElementSize;
- break;
- }
- }
- else
- snapPoint += averageElementSize;
- }
-
- snapPoints.Add(snapPoint);
- }
- }
- break;
- }
-
- return snapPoints;
+ return new VirtualizingSnapPointsList(_realizedElements, ItemsControl?.ItemsSource?.Count() ?? 0, orientation, Orientation, snapPointsAlignment, EstimateElementSizeU());
}
///
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 42052b8d69..dc58947994 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -169,6 +169,7 @@ namespace Avalonia.Controls
private readonly Size _maxPlatformClientSize;
private bool _shown;
private bool _showingAsDialog;
+ private bool _wasShownBefore;
///
/// Initializes static members of the class.
@@ -715,9 +716,10 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner);
- PlatformImpl?.Show(ShowActivated, false);
StartRendering();
+ PlatformImpl?.Show(ShowActivated, false);
OnOpened(EventArgs.Empty);
+ _wasShownBefore = true;
}
}
@@ -791,9 +793,8 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner);
- PlatformImpl?.Show(ShowActivated, true);
-
StartRendering();
+ PlatformImpl?.Show(ShowActivated, true);
Observable.FromEventPattern(
x => Closed += x,
@@ -872,6 +873,11 @@ namespace Avalonia.Controls
private void SetWindowStartupLocation(Window? owner = null)
{
+ if (_wasShownBefore == true)
+ {
+ return;
+ }
+
var startupLocation = WindowStartupLocation;
if (startupLocation == WindowStartupLocation.CenterOwner &&
diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs
index eff190c39e..c248116614 100644
--- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs
+++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using Avalonia.Controls;
+using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
@@ -13,6 +14,9 @@ namespace Avalonia.DesignerSupport
public class DesignWindowLoader
{
public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath)
+ => LoadDesignerWindow(xaml, assemblyPath, xamlFileProjectPath, 1.0);
+
+ public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath, double renderScaling)
{
Window window;
Control control;
@@ -96,6 +100,9 @@ namespace Avalonia.DesignerSupport
window = new Window() {Content = (Control)control};
}
+ if (window.PlatformImpl is OffscreenTopLevelImplBase offscreenImpl)
+ offscreenImpl.RenderScaling = renderScaling;
+
Design.ApplyDesignModeProperties(window, control);
if (!window.IsSet(Window.SizeToContentProperty))
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index e0fcf8e530..9463224b99 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -53,7 +53,11 @@ namespace Avalonia.DesignerSupport.Remote
// In previewer mode we completely ignore client-side viewport size
if (obj is ClientViewportAllocatedMessage alloc)
{
- Dispatcher.UIThread.Post(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY)));
+ Dispatcher.UIThread.Post(() =>
+ {
+ RenderScaling = alloc.DpiX / 96.0;
+ RenderAndSendFrameIfNeeded();
+ });
return;
}
base.OnMessage(transport, obj);
@@ -63,11 +67,11 @@ namespace Avalonia.DesignerSupport.Remote
{
_transport.Send(new RequestViewportResizeMessage
{
- Width = clientSize.Width,
- Height = clientSize.Height
+ Width = Math.Ceiling(clientSize.Width * RenderScaling),
+ Height = Math.Ceiling(clientSize.Height * RenderScaling)
});
ClientSize = clientSize;
- RenderIfNeeded();
+ RenderAndSendFrameIfNeeded();
}
public void Move(PixelPoint point)
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
index d2787e73ae..ba9dd592ce 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
@@ -52,7 +52,7 @@ namespace Avalonia.DesignerSupport.Remote
.Bind().ToConstant(Keyboard)
.Bind().ToSingleton()
.Bind().ToConstant(new ManagedDispatcherImpl(null))
- .Bind().ToConstant(new DefaultRenderTimer(60))
+ .Bind().ToConstant(new UiThreadRenderTimer(60))
.Bind().ToConstant(instance)
.Bind().ToSingleton()
.Bind().ToSingleton();
diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
index 313063269b..06d63a65ba 100644
--- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
+++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using System.Threading;
-using System.Xml;
using Avalonia.Controls;
using Avalonia.DesignerSupport.Remote.HtmlTransport;
-using Avalonia.Input;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Designer;
using Avalonia.Remote.Protocol.Viewport;
@@ -20,6 +17,7 @@ namespace Avalonia.DesignerSupport.Remote
private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats;
private static ClientViewportAllocatedMessage s_viewportAllocatedMessage;
private static ClientRenderInfoMessage s_renderInfoMessage;
+ private static double s_lastRenderScaling = 1.0;
private static IAvaloniaRemoteTransportConnection s_transport;
class CommandLineArgs
@@ -140,6 +138,7 @@ namespace Avalonia.DesignerSupport.Remote
CommandLineArgs args, object obj)
{
var builder = (AppBuilder)obj;
+ builder = builder.UseStandardRuntimePlatformSubsystem();
if (args.Method == Methods.AvaloniaRemote)
builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport));
if (args.Method == Methods.Html)
@@ -226,6 +225,9 @@ namespace Avalonia.DesignerSupport.Remote
}
if (obj is UpdateXamlMessage xaml)
{
+ if (s_currentWindow is not null)
+ s_lastRenderScaling = s_currentWindow.RenderScaling;
+
try
{
s_currentWindow?.Close();
@@ -237,7 +239,7 @@ namespace Avalonia.DesignerSupport.Remote
s_currentWindow = null;
try
{
- s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath, xaml.XamlFileProjectPath);
+ s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath, xaml.XamlFileProjectPath, s_lastRenderScaling);
s_transport.Send(new UpdateXamlResultMessage(){Handle = s_currentWindow.PlatformImpl?.Handle?.Handle.ToString()});
}
catch (Exception e)
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml
new file mode 100644
index 0000000000..7162be888f
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs
new file mode 100644
index 0000000000..268e42cff3
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Globalization;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+
+namespace Avalonia.Diagnostics.Controls
+{
+ [TemplatePart("PART_ClearButton", typeof(Button))]
+ partial class BrushEditor : TemplatedControl
+ {
+ private readonly EventHandler clearHandler;
+ private Button? _clearButton = default;
+ private readonly ColorView _colorView = new()
+ {
+ HexInputAlphaPosition = AlphaComponentPosition.Leading, // Always match XAML
+ };
+
+ public BrushEditor()
+ {
+ FlyoutBase.SetAttachedFlyout(this, new Flyout { Content = _colorView });
+ _colorView.ColorChanged += (_, e) => Brush = new ImmutableSolidColorBrush(e.NewColor);
+ clearHandler = (s, e) => Brush = default;
+ }
+
+ protected override Type StyleKeyOverride => typeof(BrushEditor);
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ if (_clearButton is not null)
+ {
+ _clearButton.Click -= clearHandler;
+ }
+ _clearButton = e.NameScope.Find