diff --git a/.editorconfig b/.editorconfig
index 62a533e468..d5b2badfd5 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -208,6 +208,12 @@ dotnet_diagnostic.AVA2001.severity = error
# Xaml files
[*.{xaml,axaml}]
indent_size = 2
+# DuplicateSetterError
+avalonia_xaml_diagnostic.AVLN2203.severity = error
+# StyleInMergedDictionaries
+avalonia_xaml_diagnostic.AVLN2204.severity = error
+# Obsolete
+avalonia_xaml_diagnostic.AVLN5001.severity = error
# Xml project files
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
diff --git a/api/Avalonia.Themes.Fluent.nupkg.xml b/api/Avalonia.Themes.Fluent.nupkg.xml
index f35bf5a629..a5be5ceeb9 100644
--- a/api/Avalonia.Themes.Fluent.nupkg.xml
+++ b/api/Avalonia.Themes.Fluent.nupkg.xml
@@ -403,6 +403,12 @@
baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/UserControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Fluent.dll
+ target/netstandard2.0/Avalonia.Themes.Fluent.dll
+
CP0001
T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Window.xaml
diff --git a/api/Avalonia.Themes.Simple.nupkg.xml b/api/Avalonia.Themes.Simple.nupkg.xml
index 78d8c4b332..e6a62495d9 100644
--- a/api/Avalonia.Themes.Simple.nupkg.xml
+++ b/api/Avalonia.Themes.Simple.nupkg.xml
@@ -391,6 +391,12 @@
baseline/netstandard2.0/Avalonia.Themes.Simple.dll
target/netstandard2.0/Avalonia.Themes.Simple.dll
+
+ CP0001
+ T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/UserControl.xaml
+ baseline/netstandard2.0/Avalonia.Themes.Simple.dll
+ target/netstandard2.0/Avalonia.Themes.Simple.dll
+
CP0001
T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Window.xaml
diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 2230532c05..9b8e28783f 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1003,6 +1003,72 @@
baseline/designer/Avalonia.Designer.HostApp.dll
target/designer/Avalonia.Designer.HostApp.dll
+
+ CP0001
+ T:Avalonia.AvaloniaLocator.RegistrationHelper`1
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.AvaloniaLocator.ResolverDisposable
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.FocusManager.<GetFocusScopeAncestors>d__18
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager.<>c
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager.ArrangeResult
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager.EffectiveViewportChangedListener
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.DefaultRenderTimer.<>c__DisplayClass13_0
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.UiThreadRenderTimer.<>c__DisplayClass3_0
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.PopupPositioning.ManagedPopupPositioner.<>c__DisplayClass5_0
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.PopupPositioning.ManagedPopupPositionerPopupImplHelper.<>c
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
+
+ CP0001
+ T:Avalonia.Controls.Primitives.PopupPositioning.ManagedPopupPositionerPopupImplHelper.MoveResizeDelegate
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
CP0006
P:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.ImportCompleted
diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml
index 39923ca781..3613518dc1 100644
--- a/azure-pipelines-integrationtests.yml
+++ b/azure-pipelines-integrationtests.yml
@@ -5,14 +5,16 @@ jobs:
steps:
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.415'
+ displayName: 'Use .NET 6.0 Runtime'
inputs:
- version: 6.0.415
+ packageType: runtime
+ version: 6.0.x
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.402'
+ displayName: 'Use .NET 7.0 SDK'
inputs:
- version: 7.0.402
+ packageType: sdk
+ useGlobalJson: true
- script: system_profiler SPDisplaysDataType |grep Resolution
displayName: 'Get Resolution'
@@ -57,14 +59,16 @@ jobs:
steps:
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.404'
+ displayName: 'Use .NET 6.0 Runtime'
inputs:
- version: 6.0.404
+ packageType: runtime
+ version: 6.0.x
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.101'
+ displayName: 'Use .NET 7.0 SDK'
inputs:
- version: 7.0.101
+ packageType: sdk
+ useGlobalJson: true
- task: Windows Application Driver@0
inputs:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 345378dbf2..a306e22355 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -30,14 +30,16 @@ jobs:
vmImage: 'ubuntu-20.04'
steps:
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.415'
+ displayName: 'Use .NET 6.0 Runtime'
inputs:
- version: 6.0.415
+ packageType: runtime
+ version: 6.0.x
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.402'
+ displayName: 'Use .NET 7.0 SDK'
inputs:
- version: 7.0.402
+ packageType: sdk
+ useGlobalJson: true
- task: CmdLine@2
displayName: 'Install Workloads'
@@ -74,14 +76,16 @@ jobs:
vmImage: 'macos-12'
steps:
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.404'
+ displayName: 'Use .NET 6.0 Runtime'
inputs:
- version: 6.0.404
+ packageType: runtime
+ version: 6.0.x
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.101'
+ displayName: 'Use .NET 7.0 SDK'
inputs:
- version: 7.0.101
+ packageType: sdk
+ useGlobalJson: true
- task: CmdLine@2
displayName: 'Install Workloads'
@@ -152,14 +156,16 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 6.0.404'
+ displayName: 'Use .NET 6.0 Runtime'
inputs:
- version: 6.0.404
+ packageType: runtime
+ version: 6.0.x
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.101'
+ displayName: 'Use .NET 7.0 SDK'
inputs:
- version: 7.0.101
+ packageType: sdk
+ useGlobalJson: true
- task: CmdLine@2
displayName: 'Install Workloads'
diff --git a/build/BuildTargets.targets b/build/BuildTargets.targets
index 481dbf06b2..13b002d523 100644
--- a/build/BuildTargets.targets
+++ b/build/BuildTargets.targets
@@ -4,6 +4,7 @@
true
true
true
+ true
diff --git a/build/DevAnalyzers.props b/build/DevAnalyzers.props
index dffd3098c3..fd416e3291 100644
--- a/build/DevAnalyzers.props
+++ b/build/DevAnalyzers.props
@@ -3,12 +3,10 @@
+ OutputItemType="Analyzer" />
+ OutputItemType="Analyzer" />
diff --git a/build/TrimmingEnable.props b/build/TrimmingEnable.props
index 3f873e3345..fcd4b45e4b 100644
--- a/build/TrimmingEnable.props
+++ b/build/TrimmingEnable.props
@@ -1,10 +1,12 @@
+
false
true
false
true
+
true
@@ -13,4 +15,11 @@
$(WarningsAsErrors);IL3050;IL3051;IL3052;IL3053;IL3054;IL3055;IL3056
+
+
+
+ true
+ false
+
+
diff --git a/build/XUnit.props b/build/XUnit.props
index 3c89c8b52b..8f991ae442 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -6,12 +6,13 @@
-
+
$(MSBuildThisFileDirectory)\avalonia.snk
False
+ $(NoWarn);CS8002
diff --git a/global.json b/global.json
index 8536535b51..13a6c6d6ad 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "7.0.101",
+ "version": "7.0.404",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
diff --git a/nukebuild/.editorconfig b/nukebuild/.editorconfig
index d6009b3c0f..c11849c8e9 100644
--- a/nukebuild/.editorconfig
+++ b/nukebuild/.editorconfig
@@ -6,3 +6,12 @@ root = false
# C# files
[*.cs]
dotnet_style_require_accessibility_modifiers = never
+
+[{il-repack,Numerge}/**/*.cs]
+dotnet_diagnostic.CA1304.severity = none
+dotnet_diagnostic.CA1815.severity = none
+dotnet_diagnostic.CA1820.severity = none
+dotnet_diagnostic.CA1825.severity = none
+dotnet_diagnostic.CA1829.severity = none
+dotnet_diagnostic.CA1847.severity = none
+dotnet_diagnostic.SYSLIB0017.severity = none
diff --git a/nukebuild/ApiDiffValidation.cs b/nukebuild/ApiDiffValidation.cs
index c8115a8113..3c58215d26 100644
--- a/nukebuild/ApiDiffValidation.cs
+++ b/nukebuild/ApiDiffValidation.cs
@@ -8,6 +8,7 @@ using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Nuke.Common.Tooling;
+using static Serilog.Log;
public static class ApiDiffValidation
{
@@ -142,7 +143,7 @@ public static class ApiDiffValidation
Avalonia.11.0.0
*/
var packageId = GetPackageId(packagePath);
- Build.Information("Downloading {0} {1} baseline package", packageId, baselineVersion);
+ Information("Downloading {0} {1} baseline package", packageId, baselineVersion);
try
{
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index f8fbf64e83..8b5cc4292a 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -1,21 +1,14 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
-using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Nuke.Common;
-using Nuke.Common.Git;
-using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
-using Nuke.Common.Tools.MSBuild;
using Nuke.Common.Tools.Npm;
-using Nuke.Common.Utilities;
-using Nuke.Common.Utilities.Collections;
using static Nuke.Common.EnvironmentInfo;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
@@ -23,6 +16,7 @@ using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.Xunit.XunitTasks;
using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
+using static Serilog.Log;
using MicroCom.CodeGenerator;
/*
diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs
index e93070e2f0..f103f16919 100644
--- a/nukebuild/RefAssemblyGenerator.cs
+++ b/nukebuild/RefAssemblyGenerator.cs
@@ -70,9 +70,6 @@ public class RefAssemblyGenerator
static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor)
{
- foreach (var nested in type.NestedTypes)
- ProcessType(nested, obsoleteCtor);
-
var hideMembers = (type.IsInterface && type.Name.EndsWith("Impl"))
|| HasPrivateApi(type.CustomAttributes);
@@ -97,7 +94,7 @@ public class RefAssemblyGenerator
foreach (var m in type.Methods)
{
- if (hideMembers || HasPrivateApi(m.CustomAttributes))
+ if (!m.IsPrivate && (hideMembers || HasPrivateApi(m.CustomAttributes)))
{
HideMethod(m);
}
@@ -108,30 +105,27 @@ public class RefAssemblyGenerator
{
if (HasPrivateApi(p.CustomAttributes))
{
- if (p.SetMethod != null)
- HideMethod(p.SetMethod);
- if (p.GetMethod != null)
- HideMethod(p.GetMethod);
+ if (p.SetMethod is { IsPrivate: false } setMethod)
+ HideMethod(setMethod);
+ if (p.GetMethod is { IsPrivate: false } getMethod)
+ HideMethod(getMethod);
}
}
foreach (var f in type.Fields)
{
- if (hideMembers || HasPrivateApi(f.CustomAttributes))
+ if (!f.IsPrivate && (hideMembers || HasPrivateApi(f.CustomAttributes)))
{
- var dflags = FieldAttributes.Public | FieldAttributes.Family | FieldAttributes.FamORAssem |
- FieldAttributes.FamANDAssem | FieldAttributes.Assembly;
- f.Attributes = ((f.Attributes | dflags) ^ dflags) | FieldAttributes.Assembly;
+ f.IsAssembly = true;
}
}
foreach (var cl in type.NestedTypes)
{
ProcessType(cl, obsoleteCtor);
- if (hideMembers)
+ if (hideMembers && cl.IsNestedPublic)
{
- var dflags = TypeAttributes.Public;
- cl.Attributes = ((cl.Attributes | dflags) ^ dflags) | TypeAttributes.NotPublic;
+ cl.IsNestedAssembly = true;
}
}
@@ -143,9 +137,7 @@ public class RefAssemblyGenerator
static void HideMethod(MethodDefinition m)
{
- var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
- MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
- m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
+ m.IsAssembly = true;
}
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute? unstableAttribute)
diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs
index eecfcf6da1..cd8d43a35e 100644
--- a/nukebuild/Shims.cs
+++ b/nukebuild/Shims.cs
@@ -1,24 +1,13 @@
-using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
-using Nuke.Common;
using Nuke.Common.IO;
using Numerge;
+using static Serilog.Log;
public partial class Build
{
- internal static void Information(string info)
- {
- Logger.Info(info);
- }
-
- internal static void Information(string info, params object[] args)
- {
- Logger.Info(info, args);
- }
-
private void Zip(AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable());
private void Zip(AbsolutePath target, IEnumerable paths)
@@ -44,16 +33,12 @@ public partial class Build
if (Directory.Exists(path))
{
var dirInfo = new DirectoryInfo(path);
- var rootPath = Path.GetDirectoryName(dirInfo.FullName);
+ var rootPath = Path.GetDirectoryName(dirInfo.FullName)!;
foreach(var fsEntry in dirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
{
if (fsEntry is FileInfo)
{
-#if NET6
var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName);
-#else
- var relPath = GetRelativePath(rootPath, fsEntry.FullName);
-#endif
AddFile(fsEntry.FullName, relPath);
}
}
@@ -82,27 +67,16 @@ public partial class Build
}
}
- private static string GetRelativePath(string relativeTo, string path)
- {
- var uri = new Uri(relativeTo);
- var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
- if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
- {
- rel = $".{Path.DirectorySeparatorChar}{rel}";
- }
- return rel;
- }
-
class NumergeNukeLogger : INumergeLogger
{
public void Log(NumergeLogLevel level, string message)
{
if(level == NumergeLogLevel.Error)
- Logger.Error(message);
+ Error(message);
else if (level == NumergeLogLevel.Warning)
- Logger.Warn(message);
+ Warning(message);
else
- Logger.Info(message);
+ Information(message);
}
}
}
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index ee9d8c6dfe..2232ea35af 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -130,6 +130,7 @@
$(IntermediateOutputPath)/Avalonia/original.dll
false
false
+ false
+ DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
+ VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
+ AnalyzerConfigFiles="@(EditorConfigFiles)">
-
-
-
+ DisplayName="Debug XAML Compiler"
+ Description="Allow debug XAML compilation"
+ Category="Debug" />
+
+
+
-
+
diff --git a/samples/ControlCatalog.Browser/Logo.svg b/samples/ControlCatalog.Browser/AppBundle/Logo.svg
similarity index 100%
rename from samples/ControlCatalog.Browser/Logo.svg
rename to samples/ControlCatalog.Browser/AppBundle/Logo.svg
diff --git a/samples/ControlCatalog.Browser/AppBundle/app.css b/samples/ControlCatalog.Browser/AppBundle/app.css
new file mode 100644
index 0000000000..e14dfe4487
--- /dev/null
+++ b/samples/ControlCatalog.Browser/AppBundle/app.css
@@ -0,0 +1,74 @@
+: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);
+}
+
+/* HTML styles for the splash screen */
+
+.highlight {
+ color: white;
+ font-size: 2.5rem;
+ display: block;
+}
+
+.purple {
+ color: #8b44ac;
+}
+
+.icon {
+ opacity: 0.05;
+ height: 35%;
+ width: 35%;
+ position: absolute;
+ background-repeat: no-repeat;
+ right: 0px;
+ bottom: 0px;
+ margin-right: 3%;
+ margin-bottom: 5%;
+ z-index: 5000;
+ background-position: right bottom;
+ pointer-events: none;
+}
+
+#avalonia-splash a {
+ color: whitesmoke;
+ text-decoration: none;
+}
+
+.center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+}
+
+#avalonia-splash {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ color: whitesmoke;
+ background: #1b2a4e;
+ font-family: 'Nunito', sans-serif;
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ justify-content: center;
+ align-items: center;
+}
+
+.splash-close {
+ animation: fadeout 0.25s linear forwards;
+}
+
+@keyframes fadeout {
+ 0% {
+ opacity: 100%;
+ }
+
+ 100% {
+ opacity: 0;
+ visibility: collapse;
+ }
+}
diff --git a/samples/ControlCatalog.Browser/embed.js b/samples/ControlCatalog.Browser/AppBundle/embed.js
similarity index 100%
rename from samples/ControlCatalog.Browser/embed.js
rename to samples/ControlCatalog.Browser/AppBundle/embed.js
diff --git a/samples/ControlCatalog.Browser/favicon.ico b/samples/ControlCatalog.Browser/AppBundle/favicon.ico
similarity index 100%
rename from samples/ControlCatalog.Browser/favicon.ico
rename to samples/ControlCatalog.Browser/AppBundle/favicon.ico
diff --git a/samples/ControlCatalog.Browser/AppBundle/index.html b/samples/ControlCatalog.Browser/AppBundle/index.html
new file mode 100644
index 0000000000..b35acaed5c
--- /dev/null
+++ b/samples/ControlCatalog.Browser/AppBundle/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ AvaloniaUI - ControlCatalog
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Browser/main.js b/samples/ControlCatalog.Browser/AppBundle/main.js
similarity index 68%
rename from samples/ControlCatalog.Browser/main.js
rename to samples/ControlCatalog.Browser/AppBundle/main.js
index 9d90db8bd2..9eae9fd740 100644
--- a/samples/ControlCatalog.Browser/main.js
+++ b/samples/ControlCatalog.Browser/AppBundle/main.js
@@ -1,7 +1,8 @@
// 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'
+import { dotnet } from './dotnet.js' // NET 7
+//import { dotnet } from './_framework/dotnet.js' // NET 8+
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
@@ -13,4 +14,4 @@ const dotnetRuntime = await dotnet
const config = dotnetRuntime.getConfig();
-await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);
+await dotnetRuntime.runMainAndExit(config.mainAssemblyName, [globalThis.location.href]);
diff --git a/samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj b/samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj
index c4278459f3..12338d615e 100644
--- a/samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj
+++ b/samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj
@@ -1,44 +1,24 @@
+
+
net7.0
browser-wasm
- main.js
+ AppBundle/main.js
Exe
true
true
- true
- -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0
-
-
-
- true
- true
- full
- true
- true
- -O2
- -O2
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+
-
-
+
diff --git a/samples/ControlCatalog.Browser/EmbedSample.Browser.cs b/samples/ControlCatalog.Browser/EmbedSample.Browser.cs
index c367230ddf..1bd226d578 100644
--- a/samples/ControlCatalog.Browser/EmbedSample.Browser.cs
+++ b/samples/ControlCatalog.Browser/EmbedSample.Browser.cs
@@ -4,6 +4,7 @@ using Avalonia.Platform;
using Avalonia.Browser;
using ControlCatalog.Pages;
+using System.Threading.Tasks;
namespace ControlCatalog.Browser;
@@ -25,7 +26,7 @@ public class EmbedSampleWeb : INativeDemoControl
_ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ =>
{
EmbedInterop.AddAppButton(defaultHandle.Object);
- });
+ }, TaskScheduler.FromCurrentSynchronizationContext());
return defaultHandle;
}
diff --git a/samples/ControlCatalog.Browser/Program.cs b/samples/ControlCatalog.Browser/Program.cs
index e1a4500173..919df5103c 100644
--- a/samples/ControlCatalog.Browser/Program.cs
+++ b/samples/ControlCatalog.Browser/Program.cs
@@ -1,8 +1,9 @@
+using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser;
-using Avalonia.Controls;
+using Avalonia.Logging;
using ControlCatalog;
using ControlCatalog.Browser;
@@ -12,7 +13,10 @@ internal partial class Program
{
public static async Task Main(string[] args)
{
+ Trace.Listeners.Add(new ConsoleTraceListener());
+
await BuildAvaloniaApp()
+ .LogToTrace(LogEventLevel.Warning)
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
diff --git a/samples/ControlCatalog.Browser/Roots.xml b/samples/ControlCatalog.Browser/Roots.xml
deleted file mode 100644
index b07fd86fa2..0000000000
--- a/samples/ControlCatalog.Browser/Roots.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/samples/ControlCatalog.Browser/app.css b/samples/ControlCatalog.Browser/app.css
deleted file mode 100644
index 0e6ab12461..0000000000
--- a/samples/ControlCatalog.Browser/app.css
+++ /dev/null
@@ -1,56 +0,0 @@
-: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/ControlCatalog.Browser/index.html b/samples/ControlCatalog.Browser/index.html
deleted file mode 100644
index 226ae70695..0000000000
--- a/samples/ControlCatalog.Browser/index.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
- AvaloniaUI - ControlCatalog
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 2120b03b20..e19b563fb7 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -74,6 +74,9 @@
ScrollViewer.VerticalScrollBarVisibility="Disabled">
+
+
+
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 801844a43c..31fa54a23a 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -59,7 +59,7 @@ namespace ControlCatalog
{
if (flowDirections.SelectedItem is FlowDirection flowDirection)
{
- TopLevel.GetTopLevel(this).FlowDirection = flowDirection;
+ TopLevel.GetTopLevel(this)!.FlowDirection = flowDirection;
}
};
diff --git a/samples/ControlCatalog/Pages/DataValidationPage.axaml b/samples/ControlCatalog/Pages/DataValidationPage.axaml
new file mode 100644
index 0000000000..d46562addd
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DataValidationPage.axaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs b/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs
new file mode 100644
index 0000000000..e38f85fec4
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs
@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages;
+
+public partial class DataValidationPage : UserControl
+{
+ public DataValidationPage()
+ {
+ InitializeComponent();
+ }
+}
+
diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
index 1521108278..143db003cd 100644
--- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
@@ -160,7 +160,7 @@ namespace ControlCatalog.Pages
}
else
{
- SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
+ SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result!));
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = true;
}
@@ -238,7 +238,7 @@ namespace ControlCatalog.Pages
SuggestedStartLocation = lastSelectedDirectory,
SuggestedFileName = "FileName",
DefaultExtension = fileTypes?.Any() == true ? "txt" : null,
- ShowOverwritePrompt = false
+ ShowOverwritePrompt = true
});
if (file is not null)
@@ -436,7 +436,7 @@ CanPickFolder: {storageProvider.CanPickFolder}";
{
var forceManaged = this.Get("ForceManaged").IsChecked ?? false;
return forceManaged
- ? new ManagedStorageProvider(GetWindow(), null)
+ ? new ManagedStorageProvider(GetWindow())
: GetTopLevel().StorageProvider;
}
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
index aef82768c8..b242d020b0 100644
--- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
+using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
@@ -14,26 +15,54 @@ namespace ControlCatalog.Pages
private const string CustomFormat = "application/xxx-avalonia-controlcatalog-custom";
public DragAndDropPage()
{
- this.InitializeComponent();
+ InitializeComponent();
_dropState = this.Get("DropState");
int textCount = 0;
- SetupDnd("Text", d => d.Set(DataFormats.Text,
- $"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
- SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move);
- SetupDnd("Files", async d => d.Set(DataFormats.Files, new[] { await (VisualRoot as TopLevel)!.StorageProvider.TryGetFileFromPathAsync(Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName) }), DragDropEffects.Copy);
+ SetupDnd(
+ "Text",
+ d => d.Set(DataFormats.Text, $"Text was dragged {++textCount} times"),
+ DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
+
+ SetupDnd(
+ "Custom",
+ d => d.Set(CustomFormat, "Test123"),
+ DragDropEffects.Move);
+
+ SetupDnd(
+ "Files",
+ async d =>
+ {
+ if (Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName is { } name &&
+ TopLevel.GetTopLevel(this) is { } topLevel &&
+ await topLevel.StorageProvider.TryGetFileFromPathAsync(name) is { } storageFile)
+ {
+ d.Set(DataFormats.Files, new[] { storageFile });
+ }
+ },
+ DragDropEffects.Copy);
}
- void SetupDnd(string suffix, Action factory, DragDropEffects effects)
+ private void SetupDnd(string suffix, Action factory, DragDropEffects effects) =>
+ SetupDnd(
+ suffix,
+ o =>
+ {
+ factory(o);
+ return Task.CompletedTask;
+ },
+ effects);
+
+ private void SetupDnd(string suffix, Func factory, DragDropEffects effects)
{
var dragMe = this.Get("DragMe" + suffix);
var dragState = this.Get("DragState" + suffix);
- async void DoDrag(object? sender, Avalonia.Input.PointerPressedEventArgs e)
+ async void DoDrag(object? sender, PointerPressedEventArgs e)
{
var dragData = new DataObject();
- factory(dragData);
+ await factory(dragData);
var result = await DragDrop.DoDragDrop(e, dragData, effects);
switch (result)
diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs
index c81ba4fb17..9164384eae 100644
--- a/samples/ControlCatalog/Pages/GesturePage.cs
+++ b/samples/ControlCatalog/Pages/GesturePage.cs
@@ -41,12 +41,12 @@ namespace ControlCatalog.Pages
SetPullHandlers(this.Find("RightPullZone"), true);
SetPullHandlers(this.Find("LeftPullZone"), false);
- var image = this.Find("PinchImage");
+ var image = this.Get("PinchImage");
SetPinchHandlers(image);
- var reset = this.Find
-
+
diff --git a/samples/MobileSandbox/MainView.xaml b/samples/MobileSandbox/MainView.xaml
index 5d35ec3fec..ccdfb090d2 100644
--- a/samples/MobileSandbox/MainView.xaml
+++ b/samples/MobileSandbox/MainView.xaml
@@ -8,7 +8,7 @@
-
+
diff --git a/samples/SafeAreaDemo.iOS/Info.plist b/samples/SafeAreaDemo.iOS/Info.plist
index ec04bd5a87..7ba449d525 100644
--- a/samples/SafeAreaDemo.iOS/Info.plist
+++ b/samples/SafeAreaDemo.iOS/Info.plist
@@ -39,9 +39,5 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIStatusBarHidden
-
- UIViewControllerBasedStatusBarAppearance
-
diff --git a/samples/SafeAreaDemo/App.xaml b/samples/SafeAreaDemo/App.xaml
index f5ffbdb32a..f406bfe710 100644
--- a/samples/SafeAreaDemo/App.xaml
+++ b/samples/SafeAreaDemo/App.xaml
@@ -2,9 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SafeAreaDemo"
x:Class="SafeAreaDemo.App"
- RequestedThemeVariant="Default">
-
-
+ RequestedThemeVariant="Light">
@@ -12,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/samples/SafeAreaDemo/App.xaml.cs b/samples/SafeAreaDemo/App.xaml.cs
index e23cb0e04a..536c850026 100644
--- a/samples/SafeAreaDemo/App.xaml.cs
+++ b/samples/SafeAreaDemo/App.xaml.cs
@@ -17,20 +17,14 @@ namespace SafeAreaDemo
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
- desktop.MainWindow = new MainWindow
- {
- DataContext = new MainViewModel()
- };
+ desktop.MainWindow = new MainWindow();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
- singleViewPlatform.MainView = new MainView
- {
- DataContext = new MainViewModel()
- };
+ singleViewPlatform.MainView = new MainView();
}
base.OnFrameworkInitializationCompleted();
}
}
-}
\ No newline at end of file
+}
diff --git a/samples/SafeAreaDemo/ViewModels/MainViewModel.cs b/samples/SafeAreaDemo/ViewModels/MainViewModel.cs
index fe58567171..3d826d8a9c 100644
--- a/samples/SafeAreaDemo/ViewModels/MainViewModel.cs
+++ b/samples/SafeAreaDemo/ViewModels/MainViewModel.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls;
using Avalonia.Controls.Platform;
using MiniMvvm;
@@ -7,15 +8,16 @@ namespace SafeAreaDemo.ViewModels
public class MainViewModel : ViewModelBase
{
private bool _useSafeArea = true;
- private bool _fullscreen;
+ private bool _displayEdgeToEdge;
private IInsetsManager? _insetsManager;
private bool _hideSystemBars;
+ private bool _autoSafeAreaPadding;
public Thickness SafeAreaPadding
{
get
{
- return _insetsManager?.SafeAreaPadding ?? default;
+ return !_autoSafeAreaPadding ? _insetsManager?.SafeAreaPadding ?? default : default;
}
}
@@ -40,12 +42,12 @@ namespace SafeAreaDemo.ViewModels
}
}
- public bool Fullscreen
+ public bool DisplayEdgeToEdge
{
- get => _fullscreen;
+ get => _displayEdgeToEdge;
set
{
- _fullscreen = value;
+ _displayEdgeToEdge = value;
if (_insetsManager != null)
{
@@ -76,25 +78,34 @@ namespace SafeAreaDemo.ViewModels
}
}
- internal IInsetsManager? InsetsManager
+ public bool AutoSafeAreaPadding
{
- get => _insetsManager;
+ get => _autoSafeAreaPadding;
set
{
- if (_insetsManager != null)
- {
- _insetsManager.SafeAreaChanged -= InsetsManager_SafeAreaChanged;
- }
+ _autoSafeAreaPadding = value;
+
+ RaisePropertyChanged();
+ RaiseSafeAreaChanged();
+ }
+ }
+
+ internal void Initialize(Control mainView, IInsetsManager? InsetsManager)
+ {
+ if (_insetsManager != null)
+ {
+ _insetsManager.SafeAreaChanged -= InsetsManager_SafeAreaChanged;
+ }
- _insetsManager = value;
+ _autoSafeAreaPadding = mainView.GetValue(TopLevel.AutoSafeAreaPaddingProperty);
+ _insetsManager = InsetsManager;
- if (_insetsManager != null)
- {
- _insetsManager.SafeAreaChanged += InsetsManager_SafeAreaChanged;
+ if (_insetsManager != null)
+ {
+ _insetsManager.SafeAreaChanged += InsetsManager_SafeAreaChanged;
- _insetsManager.DisplayEdgeToEdge = _fullscreen;
- _insetsManager.IsSystemBarVisible = !_hideSystemBars;
- }
+ _displayEdgeToEdge = _insetsManager.DisplayEdgeToEdge;
+ _hideSystemBars = !(_insetsManager.IsSystemBarVisible ?? false);
}
}
diff --git a/samples/SafeAreaDemo/Views/MainView.xaml b/samples/SafeAreaDemo/Views/MainView.xaml
index a8f7c2e735..966b0a02ea 100644
--- a/samples/SafeAreaDemo/Views/MainView.xaml
+++ b/samples/SafeAreaDemo/Views/MainView.xaml
@@ -7,7 +7,9 @@
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="SafeAreaDemo.Views.MainView"
- x:DataType="vm:MainViewModel">
+ x:DataType="vm:MainViewModel"
+ Background="#ccc"
+ TopLevel.AutoSafeAreaPadding="{Binding AutoSafeAreaPadding, Mode=TwoWay}">
- Fullscreen
- Use Safe Area
+ Display Edge To Edge
+ Use Safe Area
+ Automatic Paddings
Hide System Bars
diff --git a/samples/SafeAreaDemo/Views/MainView.xaml.cs b/samples/SafeAreaDemo/Views/MainView.xaml.cs
index 4b8c5e5f15..bacb721d27 100644
--- a/samples/SafeAreaDemo/Views/MainView.xaml.cs
+++ b/samples/SafeAreaDemo/Views/MainView.xaml.cs
@@ -18,10 +18,9 @@ namespace SafeAreaDemo.Views
base.OnLoaded(e);
var insetsManager = TopLevel.GetTopLevel(this)?.InsetsManager;
- if (insetsManager != null && DataContext is MainViewModel viewModel)
- {
- viewModel.InsetsManager = insetsManager;
- }
+ var viewModel = new MainViewModel();
+ viewModel.Initialize(this, insetsManager);
+ DataContext = viewModel;
}
}
}
diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
index 22387c5a05..0f11ad7442 100644
--- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
+++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
@@ -4,6 +4,7 @@
net461;net6.0-windows
true
true
+ true
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index 363ab2bc23..be93d99442 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -9,9 +9,9 @@
-
-
-
+
+
+
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
index 3fcfde3ee4..3a32c269d7 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+#nullable enable
namespace Avalonia.Android
{
@@ -11,9 +7,9 @@ namespace Avalonia.Android
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
private static AppBuilder? s_appBuilder;
- internal static object ViewContent;
+ internal static object? ViewContent;
- public object Content
+ public object? Content
{
get
{
@@ -51,7 +47,7 @@ namespace Avalonia.Android
View.Content = ViewContent;
}
- if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
+ 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 499924d8fb..a4175f864f 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -1,9 +1,8 @@
using System;
-using System.Diagnostics;
+using System.Runtime.Versioning;
using Android.App;
using Android.Content;
using Android.Content.PM;
-using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
@@ -37,6 +36,7 @@ namespace Avalonia.Android
ActivityResult?.Invoke(requestCode, resultCode, data);
}
+ [SupportedOSPlatform("android23.0")]
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -47,7 +47,8 @@ namespace Avalonia.Android
public abstract partial class AvaloniaMainActivity : AvaloniaMainActivity where TApp : Application, new()
{
- internal AvaloniaView View;
+ internal AvaloniaView View { get; set; }
+
private GlobalLayoutListener _listener;
protected override void OnCreate(Bundle savedInstanceState)
@@ -68,9 +69,9 @@ namespace Avalonia.Android
base.OnResume();
// Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
- if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
{
- Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
+ attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
}
}
@@ -83,9 +84,9 @@ namespace Avalonia.Android
base.OnDestroy();
}
- class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
+ private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
- private AvaloniaView _view;
+ private readonly AvaloniaView _view;
public GlobalLayoutListener(AvaloniaView view)
{
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index 05ec1795f4..3d3cd2995d 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.Versioning;
using Android.Content;
using Android.Content.Res;
using Android.Runtime;
@@ -46,6 +47,7 @@ namespace Avalonia.Android
return _view.View.DispatchKeyEvent(e);
}
+ [SupportedOSPlatform("android24.0")]
public override void OnVisibilityAggregated(bool isVisible)
{
base.OnVisibilityAggregated(isVisible);
diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
index 251a177432..6d8ae873a2 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
@@ -30,17 +30,25 @@ namespace Avalonia.Android.Platform
{
_displayEdgeToEdge = value;
- if(Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ var window = _activity.Window;
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(28) && window?.Attributes is { } attributes)
{
- _activity.Window.Attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
+ attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
}
- WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
+ if (window is not null)
+ {
+ WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
+ }
if(value)
{
- _activity.Window.AddFlags(WindowManagerFlags.TranslucentStatus);
- _activity.Window.AddFlags(WindowManagerFlags.TranslucentNavigation);
+ if (window is not null)
+ {
+ window.AddFlags(WindowManagerFlags.TranslucentStatus);
+ window.AddFlags(WindowManagerFlags.TranslucentNavigation);
+ }
}
else
{
@@ -57,14 +65,17 @@ namespace Avalonia.Android.Platform
_callback.InsetsManager = this;
- ViewCompat.SetOnApplyWindowInsetsListener(_activity.Window.DecorView, this);
+ if (_activity.Window is { } window)
+ {
+ ViewCompat.SetOnApplyWindowInsetsListener(window.DecorView, this);
- ViewCompat.SetWindowInsetsAnimationCallback(_activity.Window.DecorView, _callback);
+ ViewCompat.SetWindowInsetsAnimationCallback(window.DecorView, _callback);
+ }
- if(Build.VERSION.SdkInt < BuildVersionCodes.R)
+ if (Build.VERSION.SdkInt < BuildVersionCodes.R)
{
_usesLegacyLayouts = true;
- _activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
+ _activity.Window?.DecorView.ViewTreeObserver?.AddOnGlobalLayoutListener(this);
}
DisplayEdgeToEdge = false;
@@ -74,7 +85,7 @@ namespace Avalonia.Android.Platform
{
get
{
- var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
+ var insets = _activity.Window is { } window ? ViewCompat.GetRootWindowInsets(window.DecorView) : null;
if (insets != null)
{
@@ -127,7 +138,7 @@ namespace Avalonia.Android.Platform
return compat.AppearanceLightStatusBars ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
}
- catch (Exception _)
+ catch (Exception)
{
return Controls.Platform.SystemBarTheme.Light;
}
@@ -136,8 +147,6 @@ namespace Avalonia.Android.Platform
{
_statusBarTheme = value;
- var isDefault = _statusBarTheme == null;
-
if (!_topLevel.View.IsShown)
{
return;
@@ -150,7 +159,7 @@ namespace Avalonia.Android.Platform
_isDefaultSystemBarLightTheme = compat.AppearanceLightStatusBars;
}
- if (value == null && _isDefaultSystemBarLightTheme != null)
+ if (value == null)
{
value = (bool)_isDefaultSystemBarLightTheme ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
}
diff --git a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
index 4ed91c248d..880918bebe 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+
+using System;
using Avalonia.Interactivity;
using Avalonia.Platform;
@@ -6,7 +8,7 @@ namespace Avalonia.Android.Platform
{
internal class AndroidSystemNavigationManagerImpl : ISystemNavigationManagerImpl
{
- public event EventHandler BackRequested;
+ public event EventHandler? BackRequested;
public AndroidSystemNavigationManagerImpl(IActivityNavigationService? navigationService)
{
@@ -16,7 +18,7 @@ namespace Avalonia.Android.Platform
}
}
- private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e)
+ private void OnBackRequested(object? sender, AndroidBackRequestedEventArgs e)
{
var routedEventArgs = new RoutedEventArgs();
diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
index 8564363889..0f2f985e27 100644
--- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
@@ -1,8 +1,8 @@
+#nullable enable
+
using System;
using System.Threading.Tasks;
-
using Android.Content;
-
using Avalonia.Input;
using Avalonia.Input.Platform;
@@ -10,34 +10,34 @@ namespace Avalonia.Android.Platform
{
internal class ClipboardImpl : IClipboard
{
- private ClipboardManager? _clipboardManager;
+ private readonly ClipboardManager? _clipboardManager;
internal ClipboardImpl(ClipboardManager? value)
{
_clipboardManager = value;
}
- public Task GetTextAsync()
+ public Task GetTextAsync()
{
if (_clipboardManager?.HasPrimaryClip == true)
{
- return Task.FromResult(_clipboardManager.PrimaryClip.GetItemAt(0).Text);
+ return Task.FromResult(_clipboardManager.PrimaryClip?.GetItemAt(0)?.Text);
}
- return Task.FromResult(null);
+ return Task.FromResult(null);
}
- public Task SetTextAsync(string text)
+ public Task SetTextAsync(string? text)
{
if(_clipboardManager == null)
{
return Task.CompletedTask;
}
- ClipData clip = ClipData.NewPlainText("text", text);
+ var clip = ClipData.NewPlainText("text", text);
_clipboardManager.PrimaryClip = clip;
- return Task.FromResult(null);
+ return Task.CompletedTask;
}
public Task ClearAsync()
@@ -49,13 +49,13 @@ namespace Avalonia.Android.Platform
_clipboardManager.PrimaryClip = null;
- return Task.FromResult(null);
+ return Task.CompletedTask;
}
public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException();
public Task GetFormatsAsync() => throw new PlatformNotSupportedException();
- public Task GetDataAsync(string format) => throw new PlatformNotSupportedException();
+ public Task GetDataAsync(string format) => throw new PlatformNotSupportedException();
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index a6e8d10777..d1ef928367 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -347,7 +347,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
activity.SetTranslucent(true);
SetBlurBehind(activity, 0);
- activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
+ activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
}
}
else if (level == WindowTransparencyLevel.Blur)
@@ -448,8 +448,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly AvaloniaInputConnection _inputConnection;
- public event EventHandler SelectionChanged;
-
public EditableWrapper(AvaloniaInputConnection inputConnection)
{
_inputConnection = inputConnection;
@@ -624,6 +622,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly);
break;
}
+ case ImeAction.Next:
+ {
+ FocusManager.GetFocusManager(_toplevel.InputRoot)?
+ .TryMoveFocus(NavigationDirection.Next);
+ break;
+ }
}
return base.PerformEditorAction(actionCode);
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
index 1dcd50d6c2..3dda301b6b 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
@@ -55,7 +55,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var keySymbol = GetKeySymbol(e.UnicodeChar, physicalKey);
var rawKeyEvent = new RawKeyEventArgs(
- AndroidKeyboardDevice.Instance,
+ AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
@@ -64,18 +64,18 @@ namespace Avalonia.Android.Platform.Specific.Helpers
physicalKey,
keySymbol);
- _view.Input(rawKeyEvent);
+ _view.Input?.Invoke(rawKeyEvent);
if ((e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
|| unicodeTextInput != null)
{
var rawTextEvent = new RawTextInputEventArgs(
- AndroidKeyboardDevice.Instance,
+ AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
);
- _view.Input(rawTextEvent);
+ _view.Input?.Invoke(rawTextEvent);
}
if (e.Action == KeyEventActions.Up)
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
index f0c22d7f38..54bcb4b196 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
@@ -172,29 +172,29 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{
}
- public async Task CreateFileAsync(string name)
+ public Task CreateFileAsync(string name)
{
var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream";
- var newFile = Document.CreateFile(mimeType, name);
+ var newFile = Document?.CreateFile(mimeType, name);
if(newFile == null)
{
- return null;
+ return Task.FromResult(null);
}
- return new AndroidStorageFile(Activity, newFile.Uri, this);
+ return Task.FromResult(new AndroidStorageFile(Activity, newFile.Uri, this));
}
- public async Task CreateFolderAsync(string name)
+ public Task CreateFolderAsync(string name)
{
var newFolder = Document?.CreateDirectory(name);
if (newFolder == null)
{
- return null;
+ return Task.FromResult(null);
}
- return new AndroidStorageFolder(Activity, newFolder.Uri, false, this, PermissionRoot);
+ return Task.FromResult(new AndroidStorageFolder(Activity, newFolder.Uri, false, this, PermissionRoot));
}
public override async Task DeleteAsync()
@@ -286,15 +286,15 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
return null;
- async Task MoveRecursively(AndroidStorageFolder storageFolder, AndroidStorageFolder destination)
+ static async Task MoveRecursively(AndroidStorageFolder storageFolder, AndroidStorageFolder destination)
{
- destination = await destination.CreateFolderAsync(storageFolder.Name) as AndroidStorageFolder;
-
- if (destination == null)
+ if (await destination.CreateFolderAsync(storageFolder.Name) is not AndroidStorageFolder newDestination)
{
return null;
}
+ destination = newDestination;
+
await foreach (var file in storageFolder.GetItemsAsync())
{
if (file is AndroidStorageFolder folder)
@@ -477,25 +477,32 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
if (Activity != null && destination is AndroidStorageFolder storageFolder)
{
- if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
+ AndroidUri? movedUri = null;
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(24))
{
try
{
- var uri = DocumentsContract.MoveDocument(Activity.ContentResolver!, Uri, ((await GetParentAsync()) as AndroidStorageFolder)!.Uri, storageFolder.Document!.Uri);
-
- return new AndroidStorageFile(Activity, uri, storageFolder);
+ if (Activity.ContentResolver is { } contentResolver &&
+ storageFolder.Document?.Uri is { } targetParentUri &&
+ await GetParentAsync() is AndroidStorageFolder parentFolder)
+ {
+ movedUri = DocumentsContract.MoveDocument(contentResolver, Uri, parentFolder.Uri, targetParentUri);
+ }
}
- catch (Exception ex)
+ catch (Exception)
{
// There are many reason why DocumentContract will fail to move a file. We fallback to copying.
return await MoveFileByCopy();
-
}
}
- else
+
+ if (movedUri is not null)
{
- return await MoveFileByCopy();
+ return new AndroidStorageFile(Activity, movedUri, storageFolder);
}
+
+ return await MoveFileByCopy();
}
async Task MoveFileByCopy()
diff --git a/src/Avalonia.Base/Animation/PageSlide.cs b/src/Avalonia.Base/Animation/PageSlide.cs
index f388de5794..402f57885f 100644
--- a/src/Avalonia.Base/Animation/PageSlide.cs
+++ b/src/Avalonia.Base/Animation/PageSlide.cs
@@ -47,7 +47,7 @@ namespace Avalonia.Animation
public TimeSpan Duration { get; set; }
///
- /// Gets the duration of the animation.
+ /// Gets the orientation of the animation.
///
public SlideAxis Orientation { get; set; }
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index 5c674be880..47b67459e1 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
-using System.Linq;
using Avalonia.Diagnostics;
namespace Avalonia.Collections
@@ -14,13 +13,13 @@ namespace Avalonia.Collections
public enum ResetBehavior
{
///
- /// Clearing the list notifies with the event with a
+ /// Clearing the list notifies with the event with a
/// action.
///
Reset,
///
- /// Clearing the list notifies with the event with a
+ /// Clearing the list notifies with the event with a
/// action.
///
Remove,
diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs
index c3316eb278..b3440aaf9a 100644
--- a/src/Avalonia.Base/Input/FocusManager.cs
+++ b/src/Avalonia.Base/Input/FocusManager.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree;
@@ -15,10 +12,16 @@ namespace Avalonia.Input
public class FocusManager : IFocusManager
{
///
- /// The focus scopes in which the focus is currently defined.
+ /// Private attached property for storing the currently focused element in a focus scope.
///
- private readonly ConditionalWeakTable _focusScopes =
- new ConditionalWeakTable();
+ ///
+ /// This property is set on the control which defines a focus scope and tracks the currently
+ /// focused element within that scope.
+ ///
+ private static readonly AttachedProperty FocusedElementProperty =
+ AvaloniaProperty.RegisterAttached("FocusedElement");
+
+ private StyledElement? _focusRoot;
///
/// Initializes a new instance of the class.
@@ -38,15 +41,6 @@ namespace Avalonia.Input
///
public IInputElement? GetFocusedElement() => Current;
- ///
- /// Gets the current focus scope.
- ///
- public IFocusScope? Scope
- {
- get;
- private set;
- }
-
///
/// Focuses a control.
///
@@ -58,38 +52,35 @@ namespace Avalonia.Input
NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None)
{
- if (control != null)
+ if (KeyboardDevice.Instance is not { } keyboardDevice)
+ return false;
+
+ if (control is not null)
{
- var scope = GetFocusScopeAncestors(control)
- .FirstOrDefault();
+ if (!CanFocus(control))
+ return false;
- if (scope != null)
+ if (GetFocusScope(control) is StyledElement scope)
{
- Scope = scope;
- return SetFocusedElement(scope, control, method, keyModifiers);
+ scope.SetValue(FocusedElementProperty, control);
+ _focusRoot = GetFocusRoot(scope);
}
+
+ keyboardDevice.SetFocusedElement(control, method, keyModifiers);
+ return true;
}
- else if (Current != null)
+ else if (_focusRoot?.GetValue(FocusedElementProperty) is { } restore &&
+ restore != Current &&
+ Focus(restore))
{
- // If control is null, set focus to the topmost focus scope.
- foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToArray())
- {
- if (scope != Scope &&
- _focusScopes.TryGetValue(scope, out var element) &&
- element != null)
- {
- return Focus(element, method);
- }
- }
-
- if (Scope is object)
- {
- // Couldn't find a focus scope, clear focus.
- return SetFocusedElement(Scope, null);
- }
+ return true;
+ }
+ else
+ {
+ _focusRoot = null;
+ keyboardDevice.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
+ return false;
}
-
- return false;
}
public void ClearFocus()
@@ -97,55 +88,23 @@ namespace Avalonia.Input
Focus(null);
}
- public IInputElement? GetFocusedElement(IFocusScope scope)
- {
- _focusScopes.TryGetValue(scope, out var result);
- return result;
- }
-
- ///
- /// Sets the currently focused element in the specified scope.
- ///
- /// The focus scope.
- /// The element to focus. May be null.
- /// The method by which focus was changed.
- /// Any key modifiers active at the time of focus.
- ///
- /// If the specified scope is the current then the keyboard focus
- /// will change.
- ///
- public bool SetFocusedElement(
- IFocusScope scope,
- IInputElement? element,
- NavigationMethod method = NavigationMethod.Unspecified,
- KeyModifiers keyModifiers = KeyModifiers.None)
+ public void ClearFocusOnElementRemoved(IInputElement removedElement, Visual oldParent)
{
- scope = scope ?? throw new ArgumentNullException(nameof(scope));
-
- if (element is not null && !CanFocus(element))
- {
- return false;
- }
-
- if (_focusScopes.TryGetValue(scope, out var existingElement))
- {
- if (element != existingElement)
- {
- _focusScopes.Remove(scope);
- _focusScopes.Add(scope, element);
- }
- }
- else
+ if (oldParent is IInputElement parentElement &&
+ GetFocusScope(parentElement) is StyledElement scope &&
+ scope.GetValue(FocusedElementProperty) is IInputElement focused &&
+ focused == removedElement)
{
- _focusScopes.Add(scope, element);
+ scope.ClearValue(FocusedElementProperty);
}
- if (Scope == scope)
- {
- KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers);
- }
+ if (Current == removedElement)
+ Focus(null);
+ }
- return true;
+ public IInputElement? GetFocusedElement(IFocusScope scope)
+ {
+ return (scope as StyledElement)?.GetValue(FocusedElementProperty);
}
///
@@ -154,35 +113,23 @@ namespace Avalonia.Input
/// The new focus scope.
public void SetFocusScope(IFocusScope scope)
{
- scope = scope ?? throw new ArgumentNullException(nameof(scope));
-
- if (!_focusScopes.TryGetValue(scope, out var e))
+ if (GetFocusedElement(scope) is { } focused)
+ {
+ Focus(focused);
+ }
+ else if (scope is IInputElement scopeElement && CanFocus(scopeElement))
{
// TODO: Make this do something useful, i.e. select the first focusable
// control, select a control that the user has specified to have default
// focus etc.
- e = scope as IInputElement;
- _focusScopes.Add(scope, e);
+ Focus(scopeElement);
}
-
- Scope = scope;
- Focus(e);
}
- public void RemoveFocusScope(IFocusScope scope)
+ public void RemoveFocusRoot(IFocusScope scope)
{
- scope = scope ?? throw new ArgumentNullException(nameof(scope));
-
- if (_focusScopes.TryGetValue(scope, out _))
- {
- SetFocusedElement(scope, null);
- _focusScopes.Remove(scope);
- }
-
- if (Scope == scope)
- {
- Scope = null;
- }
+ if (scope == _focusRoot)
+ ClearFocus();
}
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
@@ -200,6 +147,17 @@ namespace Avalonia.Input
// In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService();
}
+
+ internal bool TryMoveFocus(NavigationDirection direction)
+ {
+ if (GetFocusedElement() is {} focusedElement
+ && KeyboardNavigationHandler.GetNext(focusedElement, direction) is {} newElement)
+ {
+ return newElement.Focus();
+ }
+
+ return false;
+ }
///
/// Checks if the specified element can be focused.
@@ -209,27 +167,45 @@ namespace Avalonia.Input
private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && IsVisible(e);
///
- /// Gets the focus scope ancestors of the specified control, traversing popups.
+ /// Gets the focus scope of the specified control, traversing popups.
///
/// The control.
- /// The focus scopes.
- private static IEnumerable GetFocusScopeAncestors(IInputElement control)
+ /// The focus scope.
+ private static StyledElement? GetFocusScope(IInputElement control)
{
IInputElement? c = control;
while (c != null)
{
- if (c is IFocusScope scope &&
+ if (c is IFocusScope &&
c is Visual v &&
v.VisualRoot is Visual root &&
root.IsVisible)
{
- yield return scope;
+ return v;
}
c = (c as Visual)?.GetVisualParent() ??
((c as IHostedVisualTreeRoot)?.Host as IInputElement);
}
+
+ return null;
+ }
+
+ private static StyledElement? GetFocusRoot(StyledElement scope)
+ {
+ if (scope is not Visual v)
+ return null;
+
+ var root = v.VisualRoot as Visual;
+
+ while (root is IHostedVisualTreeRoot hosted &&
+ hosted.Host?.VisualRoot is Visual parentRoot)
+ {
+ root = parentRoot;
+ }
+
+ return root;
}
///
@@ -263,6 +239,11 @@ namespace Avalonia.Input
}
}
- private static bool IsVisible(IInputElement e) => (e as Visual)?.IsEffectivelyVisible ?? true;
+ private static bool IsVisible(IInputElement e)
+ {
+ if (e is Visual v)
+ return v.IsAttachedToVisualTree && e.IsEffectivelyVisible;
+ return true;
+ }
}
}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
index 2cb97b4707..796570e090 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
@@ -2,6 +2,7 @@
{
public abstract class GestureRecognizer : StyledElement
{
+ private PointerEventArgs? _currentPointerEventArgs;
protected internal IInputElement? Target { get; internal set; }
protected abstract void PointerPressed(PointerPressedEventArgs e);
@@ -11,17 +12,23 @@
internal void PointerPressedInternal(PointerPressedEventArgs e)
{
+ _currentPointerEventArgs = e;
PointerPressed(e);
+ _currentPointerEventArgs = null;
}
internal void PointerReleasedInternal(PointerReleasedEventArgs e)
{
+ _currentPointerEventArgs = e;
PointerReleased(e);
+ _currentPointerEventArgs = null;
}
internal void PointerMovedInternal(PointerEventArgs e)
{
+ _currentPointerEventArgs = e;
PointerMoved(e);
+ _currentPointerEventArgs = null;
}
internal void PointerCaptureLostInternal(IPointer pointer)
@@ -32,6 +39,8 @@
protected void Capture(IPointer pointer)
{
(pointer as Pointer)?.CaptureGestureRecognizer(this);
+
+ _currentPointerEventArgs?.PreventGestureRecognition();
}
}
}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
index d628830187..6cb0a57cf2 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
@@ -10,6 +10,7 @@ namespace Avalonia.Input
private IPointer? _secondContact;
private Point _secondPoint;
private Point _origin;
+ private double _previousAngle;
protected override void PointerCaptureLost(IPointer pointer)
{
@@ -20,7 +21,7 @@ namespace Avalonia.Input
{
if (Target is Visual visual)
{
- if(_firstContact == e.Pointer)
+ if (_firstContact == e.Pointer)
{
_firstPoint = e.GetPosition(visual);
}
@@ -39,10 +40,13 @@ namespace Avalonia.Input
var scale = distance / _initialDistance;
- var pinchEventArgs = new PinchEventArgs(scale, _origin);
- Target?.RaiseEvent(pinchEventArgs);
+ var degree = GetAngleDegreeFromPoints(_firstPoint, _secondPoint);
+ var pinchEventArgs = new PinchEventArgs(scale, _origin, degree, _previousAngle - degree);
+ _previousAngle = degree;
+ Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled;
+ e.PreventGestureRecognition();
}
}
}
@@ -74,18 +78,24 @@ namespace Avalonia.Input
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
+ _previousAngle = GetAngleDegreeFromPoints(_firstPoint, _secondPoint);
+
Capture(_firstContact);
Capture(_secondContact);
+ e.PreventGestureRecognition();
}
}
}
protected override void PointerReleased(PointerReleasedEventArgs e)
{
- RemoveContact(e.Pointer);
+ if(RemoveContact(e.Pointer))
+ {
+ e.PreventGestureRecognition();
+ }
}
- private void RemoveContact(IPointer pointer)
+ private bool RemoveContact(IPointer pointer)
{
if (_firstContact == pointer || _secondContact == pointer)
{
@@ -102,13 +112,28 @@ namespace Avalonia.Input
}
Target?.RaiseEvent(new PinchEndedEventArgs());
+ return true;
}
+ return false;
}
- private float GetDistance(Point a, Point b)
+ private static float GetDistance(Point a, Point b)
{
- var length = _secondPoint - _firstPoint;
+ var length = b - a;
return (float)new Vector(length.X, length.Y).Length;
}
+
+ private static double GetAngleDegreeFromPoints(Point a, Point b)
+ {
+ // https://stackoverflow.com/a/15994225/20894223
+
+ var deltaX = a.X - b.X;
+ var deltaY = -(a.Y - b.Y); // I reverse the sign, because on the screen the Y axes
+ // are reversed with respect to the Cartesian plane.
+ var rad = System.Math.Atan2(deltaX, deltaY); // radians from -π to +π
+ var degree = ((rad * (180 / System.Math.PI))) + 180; // Atan2 returns a radian value between -π to +π, in degrees -180 to +180.
+ // To get the angle between 0 and 360 degrees you need to add 180 degrees.
+ return degree;
+ }
}
}
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
index 57faf5bfd8..ee7248cd63 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
@@ -44,7 +44,6 @@ namespace Avalonia.Input
{
var currentPosition = e.GetPosition(visual);
Capture(e.Pointer);
- e.PreventGestureRecognition();
Vector delta = default;
switch (PullDirection)
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
index beecfcc3ce..69bf8cae5f 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
@@ -124,8 +124,6 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
Capture(e.Pointer);
-
- e.PreventGestureRecognition();
}
}
diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs
index 91dae88dbb..bdb6500168 100644
--- a/src/Avalonia.Base/Input/InputElement.cs
+++ b/src/Avalonia.Base/Input/InputElement.cs
@@ -511,7 +511,7 @@ namespace Avalonia.Input
if (IsFocused)
{
- FocusManager.GetFocusManager(this)?.ClearFocus();
+ FocusManager.GetFocusManager(this)?.ClearFocusOnElementRemoved(this, e.Parent);
}
}
diff --git a/src/Avalonia.Base/Input/InputMethod.cs b/src/Avalonia.Base/Input/InputMethod.cs
index 39e7d6d31f..9df48a7d2e 100644
--- a/src/Avalonia.Base/Input/InputMethod.cs
+++ b/src/Avalonia.Base/Input/InputMethod.cs
@@ -29,7 +29,7 @@ namespace Avalonia.Input
}
///
- /// Defines the event.
+ /// Defines the TextInputMethodClientRequeryRequested event.
///
public static readonly RoutedEvent TextInputMethodClientRequeryRequestedEvent =
RoutedEvent.Register(
diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs
index 9697e32926..3004a70bdc 100644
--- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs
+++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs
@@ -649,12 +649,12 @@ namespace Avalonia.Input.Navigation
private static bool IsTabStop(IInputElement e)
{
if (e is InputElement ie)
- return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEnabled;
+ return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEffectivelyEnabled;
return false;
}
private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e);
private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true;
- private static bool IsVisibleAndEnabled(IInputElement e) => IsVisible(e) && e.IsEnabled;
+ private static bool IsVisibleAndEnabled(IInputElement e) => IsVisible(e) && e.IsEffectivelyEnabled;
}
}
diff --git a/src/Avalonia.Base/Input/PinchEventArgs.cs b/src/Avalonia.Base/Input/PinchEventArgs.cs
index 31c760eb51..be373f443a 100644
--- a/src/Avalonia.Base/Input/PinchEventArgs.cs
+++ b/src/Avalonia.Base/Input/PinchEventArgs.cs
@@ -4,20 +4,44 @@ namespace Avalonia.Input
{
public class PinchEventArgs : RoutedEventArgs
{
- public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent)
+ public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent)
{
Scale = scale;
ScaleOrigin = scaleOrigin;
}
+ public PinchEventArgs(double scale, Point scaleOrigin, double angle, double angleDelta) : base(Gestures.PinchEvent)
+ {
+ Scale = scale;
+ ScaleOrigin = scaleOrigin;
+ Angle = angle;
+ AngleDelta = angleDelta;
+ }
+
public double Scale { get; } = 1;
public Point ScaleOrigin { get; }
+
+ ///
+ /// Gets the angle of the pinch gesture, in degrees.
+ ///
+ ///
+ /// A pinch gesture is the movement of two pressed points closer together. This property is the measured angle of the line between those two points. Remember zero degrees is a line pointing up.
+ ///
+ public double Angle { get; }
+
+ ///
+ /// Gets the difference from the previous and current pinch angle.
+ ///
+ ///
+ /// The AngleDelta value includes the sign of rotation. Positive for clockwise, negative counterclockwise.
+ ///
+ public double AngleDelta { get; }
}
public class PinchEndedEventArgs : RoutedEventArgs
{
- public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
+ public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
{
}
}
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index 8e305a66ad..43692da296 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -88,7 +88,10 @@ namespace Avalonia.Input
public void Dispose()
{
- Capture(null);
+ if (Captured != null)
+ {
+ Capture(null);
+ }
}
///
diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs
index f15e87da1b..18bd21b664 100644
--- a/src/Avalonia.Base/Logging/LogArea.cs
+++ b/src/Avalonia.Base/Logging/LogArea.cs
@@ -74,5 +74,10 @@ namespace Avalonia.Logging
/// The log event comes from macOS Platform
///
public const string macOSPlatform = nameof(macOSPlatform);
+
+ ///
+ /// The log event comes from Browser Platform
+ ///
+ public static string BrowserPlatform => nameof(BrowserPlatform);
}
}
diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
index 4ddf0eb322..fc6dfb1f19 100644
--- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
@@ -284,6 +284,16 @@ namespace Avalonia.Media.Imaging
return AvaloniaLocator.Current.GetRequiredService();
}
- IRef IImageBrushSource.Bitmap => PlatformImpl;
+ IRef? IImageBrushSource.Bitmap
+ {
+ get
+ {
+ // TODO12: We should probably make PlatformImpl to be nullable or make it possible to check
+ // and fix IRef in general (right now Item is not nullable while it internally is)
+ if (PlatformImpl.Item == null!)
+ return null;
+ return PlatformImpl;
+ }
+ }
}
}
diff --git a/src/Avalonia.Base/Media/MediaContext.Compositor.cs b/src/Avalonia.Base/Media/MediaContext.Compositor.cs
index feb6fee8d6..41585ddc51 100644
--- a/src/Avalonia.Base/Media/MediaContext.Compositor.cs
+++ b/src/Avalonia.Base/Media/MediaContext.Compositor.cs
@@ -1,4 +1,6 @@
using System.Linq;
+using System.Threading.Tasks;
+
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Transport;
@@ -20,7 +22,8 @@ partial class MediaContext
_requestedCommits.Remove(compositor);
_pendingCompositionBatches[compositor] = commit;
commit.Processed.ContinueWith(_ =>
- _dispatcher.Post(() => CompositionBatchFinished(compositor, commit), DispatcherPriority.Send));
+ _dispatcher.Post(() => CompositionBatchFinished(compositor, commit), DispatcherPriority.Send),
+ TaskContinuationOptions.ExecuteSynchronously);
return commit;
}
@@ -93,7 +96,7 @@ partial class MediaContext
// Unit tests are assuming that they can call any API without setting up platforms
if (AvaloniaLocator.Current.GetService() == null)
return;
-
+
if (compositor is
{
UseUiThreadForSynchronousCommits: false,
diff --git a/src/Avalonia.Base/Media/PolyLineSegment.cs b/src/Avalonia.Base/Media/PolyLineSegment.cs
index 49c34fea66..da87a2155b 100644
--- a/src/Avalonia.Base/Media/PolyLineSegment.cs
+++ b/src/Avalonia.Base/Media/PolyLineSegment.cs
@@ -39,6 +39,8 @@ namespace Avalonia.Media
/// Initializes a new instance of the class.
///
/// The points.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
+ Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public PolyLineSegment(IEnumerable points)
{
Points = new Points(points);
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index 09042b9d60..efaab511ce 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -1299,8 +1299,6 @@ namespace Avalonia.Media.TextFormatting
}
}
- var overhangAfter = Math.Max(0, bounds.Bottom - height);
-
var width = widthIncludingWhitespace;
for (var i = _textRuns.Length - 1; i >= 0; i--)
@@ -1327,11 +1325,12 @@ namespace Avalonia.Media.TextFormatting
}
}
- var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
+ //The width of overhanging pixels at the bottom
+ var overhangAfter = Math.Max(0, bounds.Bottom - height);
//The width of overhanging pixels at the origin
- var overhangLeading = Math.Max(0, start - bounds.Left);
+ var overhangLeading = Math.Abs(Math.Min(bounds.Left, 0));
//The width of overhanging pixels at the end
- var overhangTrailing = Math.Max(0, bounds.Width - widthIncludingWhitespace);
+ var overhangTrailing = Math.Max(0, bounds.Right - widthIncludingWhitespace);
var hasOverflowed = width > _paragraphWidth;
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
@@ -1339,6 +1338,8 @@ namespace Avalonia.Media.TextFormatting
height = lineHeight;
}
+ var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
+
return new TextLineMetrics
{
HasOverflowed = hasOverflowed,
diff --git a/src/Avalonia.Base/Media/TransformGroup.cs b/src/Avalonia.Base/Media/TransformGroup.cs
index 38069f783a..bce50a7e6b 100644
--- a/src/Avalonia.Base/Media/TransformGroup.cs
+++ b/src/Avalonia.Base/Media/TransformGroup.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Collections;
using Avalonia.Metadata;
@@ -11,24 +12,20 @@ namespace Avalonia.Media
public static readonly StyledProperty ChildrenProperty =
AvaloniaProperty.Register(nameof(Children));
- [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
+ private IDisposable? _childrenNotificationSubscription;
+ private readonly EventHandler _childTransformChangedHandler;
+ private Matrix? _lastMatrix;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public TransformGroup()
{
- Children = new Transforms();
- Children.ResetBehavior = ResetBehavior.Remove;
- Children.CollectionChanged += delegate
+ _childTransformChangedHandler = (_, _) =>
{
- Children.ForEachItem(
- (tr) => tr.Changed += ChildTransform_Changed,
- (tr) => tr.Changed -= ChildTransform_Changed,
- () => { });
+ _lastMatrix = null;
+ RaiseChanged();
};
- }
-
- private void ChildTransform_Changed(object? sender, System.EventArgs e)
- {
- this.RaiseChanged();
+ Children = new Transforms();
}
///
@@ -51,14 +48,41 @@ namespace Avalonia.Media
{
get
{
- Matrix result = Matrix.Identity;
-
- foreach (var t in Children)
+ if (_lastMatrix is null)
{
- result *= t.Value;
+ var matrix = Matrix.Identity;
+ foreach (var t in Children)
+ {
+ matrix *= t.Value;
+ }
+ _lastMatrix = matrix;
}
+ return _lastMatrix.Value;
+ }
+ }
- return result;
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == ChildrenProperty)
+ {
+ _childrenNotificationSubscription?.Dispose();
+ if (change.OldValue is Transforms oldTransforms)
+ {
+ foreach (var item in oldTransforms)
+ {
+ item.Changed -= _childTransformChangedHandler;
+ }
+ }
+ if (change.NewValue is Transforms newTransforms)
+ {
+ // Ensure reset behavior is Remove
+ newTransforms.ResetBehavior = ResetBehavior.Remove;
+ _childrenNotificationSubscription = newTransforms.ForEachItem(
+ (tr) => tr.Changed += _childTransformChangedHandler,
+ (tr) => tr.Changed -= _childTransformChangedHandler,
+ () => { });
+ }
}
}
}
diff --git a/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs b/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
index 8fef283b58..ce9b38ead2 100644
--- a/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
+++ b/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+
+using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
index 0c455ab511..715fc182d6 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
@@ -94,21 +94,22 @@ internal class BclStorageFile : IStorageBookmarkFile
GC.SuppressFinalize(this);
}
- public async Task DeleteAsync()
+ public Task DeleteAsync()
{
FileInfo.Delete();
+ return Task.CompletedTask;
}
- public async Task MoveAsync(IStorageFolder destination)
+ public Task MoveAsync(IStorageFolder destination)
{
if (destination is BclStorageFolder storageFolder)
{
var newPath = System.IO.Path.Combine(storageFolder.DirectoryInfo.FullName, FileInfo.Name);
FileInfo.MoveTo(newPath);
- return new BclStorageFile(new FileInfo(newPath));
+ return Task.FromResult(new BclStorageFile(new FileInfo(newPath)));
}
- return null;
+ return Task.FromResult(null);
}
}
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
index 8c657fb787..a5727dc3cd 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
+using Avalonia.Utilities;
namespace Avalonia.Platform.Storage.FileIO;
@@ -57,17 +58,11 @@ internal class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult(null);
}
- public async IAsyncEnumerable GetItemsAsync()
- {
- var items = DirectoryInfo.EnumerateDirectories()
+ public IAsyncEnumerable GetItemsAsync()
+ => DirectoryInfo.EnumerateDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d))
- .Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)));
-
- foreach (var item in items)
- {
- yield return item;
- }
- }
+ .Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)))
+ .AsAsyncEnumerable();
public virtual Task SaveBookmarkAsync()
{
@@ -95,38 +90,39 @@ internal class BclStorageFolder : IStorageBookmarkFolder
GC.SuppressFinalize(this);
}
- public async Task DeleteAsync()
+ public Task DeleteAsync()
{
DirectoryInfo.Delete(true);
+ return Task.CompletedTask;
}
- public async Task MoveAsync(IStorageFolder destination)
+ public Task MoveAsync(IStorageFolder destination)
{
if (destination is BclStorageFolder storageFolder)
{
var newPath = System.IO.Path.Combine(storageFolder.DirectoryInfo.FullName, DirectoryInfo.Name);
DirectoryInfo.MoveTo(newPath);
- return new BclStorageFolder(new DirectoryInfo(newPath));
+ return Task.FromResult(new BclStorageFolder(new DirectoryInfo(newPath)));
}
- return null;
+ return Task.FromResult(null);
}
- public async Task CreateFileAsync(string name)
+ public Task CreateFileAsync(string name)
{
var fileName = System.IO.Path.Combine(DirectoryInfo.FullName, name);
var newFile = new FileInfo(fileName);
using var stream = newFile.Create();
- return new BclStorageFile(newFile);
+ return Task.FromResult(new BclStorageFile(newFile));
}
- public async Task CreateFolderAsync(string name)
+ public Task CreateFolderAsync(string name)
{
var newFolder = DirectoryInfo.CreateSubdirectory(name);
- return new BclStorageFolder(newFolder);
+ return Task.FromResult(new BclStorageFolder(newFolder));
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index ca00b94eaf..52892b379b 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -183,7 +183,7 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
{
_queuedSceneInvalidation = false;
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
- }, DispatcherPriority.Input));
+ }, DispatcherPriority.Input), TaskContinuationOptions.ExecuteSynchronously);
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
index ca4dfe5ed3..24817d7865 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
@@ -108,7 +108,8 @@ namespace Avalonia.Rendering.Composition
var pending = _pendingBatch;
if (pending != null)
pending.Processed.ContinueWith(
- _ => Dispatcher.Post(_triggerCommitRequested, DispatcherPriority.Send));
+ _ => Dispatcher.Post(_triggerCommitRequested, DispatcherPriority.Send),
+ TaskContinuationOptions.ExecuteSynchronously);
else
_triggerCommitRequested();
}
diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs
index 03fbf809e8..846cce7a23 100644
--- a/src/Avalonia.Base/Rendering/RenderLoop.cs
+++ b/src/Avalonia.Base/Rendering/RenderLoop.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading;
using Avalonia.Logging;
using Avalonia.Threading;
@@ -20,7 +19,6 @@ namespace Avalonia.Rendering
private readonly List _itemsCopy = new List();
private IRenderTimer? _timer;
private int _inTick;
- private readonly int _inUpdate;
public static IRenderLoop LocatorAutoInstance
{
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
index 904bd0b607..30c77bf7dd 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
@@ -120,6 +120,7 @@ public partial class Dispatcher
/// Executes the specified Func<TResult> synchronously on the
/// thread that the Dispatcher was created on.
///
+ /// The type of the return value.
///
/// A Func<TResult> delegate to invoke through the dispatcher.
///
@@ -138,6 +139,7 @@ public partial class Dispatcher
/// Executes the specified Func<TResult> synchronously on the
/// thread that the Dispatcher was created on.
///
+ /// The type of the return value.
///
/// A Func<TResult> delegate to invoke through the dispatcher.
///
@@ -158,6 +160,7 @@ public partial class Dispatcher
/// Executes the specified Func<TResult> synchronously on the
/// thread that the Dispatcher was created on.
///
+ /// The type of the return value.
///
/// A Func<TResult> delegate to invoke through the dispatcher.
///
@@ -185,6 +188,7 @@ public partial class Dispatcher
/// Executes the specified Func<TResult> synchronously on the
/// thread that the Dispatcher was created on.
///
+ /// The type of the return value.
///
/// A Func<TResult> delegate to invoke through the dispatcher.
///
@@ -319,6 +323,7 @@ public partial class Dispatcher
/// Executes the specified Func<TResult> asynchronously on the
/// thread that the Dispatcher was created on.
///
+ /// The type of the return value.
///
/// A Func<TResult> delegate to invoke through the dispatcher.
///
@@ -357,6 +362,7 @@ public partial class Dispatcher
/// Executes the specified Func<TResult> asynchronously on the
/// thread that the Dispatcher was created on.
///
+ /// The type of the return value.
///
/// A Func<TResult> delegate to invoke through the dispatcher.
///
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
index f279353b36..151d3d5fd6 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
@@ -71,16 +71,28 @@ public partial class Dispatcher
}
}
- class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
+ private sealed class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
{
public bool CurrentThreadIsLoopThread => true;
+
public void Signal()
{
}
- public event Action? Signaled;
- public event Action? Timer;
+ public event Action? Signaled
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action? Timer
+ {
+ add { }
+ remove { }
+ }
+
public long Now => 0;
+
public void UpdateTimer(long? dueTimeInMs)
{
}
diff --git a/src/Avalonia.Base/Threading/IDispatcherImpl.cs b/src/Avalonia.Base/Threading/IDispatcherImpl.cs
index e1ae8f34e2..dd438b176e 100644
--- a/src/Avalonia.Base/Threading/IDispatcherImpl.cs
+++ b/src/Avalonia.Base/Threading/IDispatcherImpl.cs
@@ -13,8 +13,8 @@ public interface IDispatcherImpl
// Asynchronously triggers Signaled callback
void Signal();
- event Action Signaled;
- event Action Timer;
+ event Action? Signaled;
+ event Action? Timer;
long Now { get; }
void UpdateTimer(long? dueTimeInMs);
}
@@ -31,7 +31,7 @@ public interface IDispatcherImplWithPendingInput : IDispatcherImpl
[PrivateApi]
public interface IDispatcherImplWithExplicitBackgroundProcessing : IDispatcherImpl
{
- event Action ReadyForBackgroundProcessing;
+ event Action? ReadyForBackgroundProcessing;
void RequestBackgroundProcessing();
}
@@ -82,7 +82,7 @@ internal class LegacyDispatcherImpl : IDispatcherImpl
}
}
-class NullDispatcherImpl : IDispatcherImpl
+internal sealed class NullDispatcherImpl : IDispatcherImpl
{
public bool CurrentThreadIsLoopThread => true;
@@ -91,8 +91,17 @@ class NullDispatcherImpl : IDispatcherImpl
}
- public event Action? Signaled;
- public event Action? Timer;
+ public event Action? Signaled
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action? Timer
+ {
+ add { }
+ remove { }
+ }
public long Now => 0;
diff --git a/src/Avalonia.Base/Utilities/AsyncEnumerableHelper.cs b/src/Avalonia.Base/Utilities/AsyncEnumerableHelper.cs
new file mode 100644
index 0000000000..2b64725563
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/AsyncEnumerableHelper.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Avalonia.Utilities;
+
+internal static class AsyncEnumerableHelper
+{
+ public static IAsyncEnumerable AsAsyncEnumerable(this IEnumerable enumerable)
+ => new EnumerableAsyncWrapper(enumerable);
+
+ private sealed class EnumerableAsyncWrapper : IAsyncEnumerable
+ {
+ private readonly IEnumerable _enumerable;
+
+ public EnumerableAsyncWrapper(IEnumerable enumerable)
+ => _enumerable = enumerable;
+
+ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
+ => new EnumeratorAsyncWrapper(_enumerable.GetEnumerator(), cancellationToken);
+ }
+
+ private sealed class EnumeratorAsyncWrapper : IAsyncEnumerator
+ {
+ private readonly IEnumerator _enumerator;
+ private readonly CancellationToken _cancellationToken;
+
+ public EnumeratorAsyncWrapper(IEnumerator enumerator, CancellationToken cancellationToken)
+ {
+ _enumerator = enumerator;
+ _cancellationToken = cancellationToken;
+ }
+
+ public T Current
+ => _enumerator.Current;
+
+ public ValueTask MoveNextAsync()
+ => _cancellationToken.IsCancellationRequested ?
+ new(Task.FromCanceled(_cancellationToken)) :
+ new(_enumerator.MoveNext());
+
+ public ValueTask DisposeAsync()
+ {
+ _enumerator.Dispose();
+ return default;
+ }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 55a72c4af9..3d83ca6a50 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -119,6 +119,7 @@
+
diff --git a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs
deleted file mode 100644
index a31c9a7516..0000000000
--- a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Avalonia.Build.Tasks
-{
- public enum BuildEngineErrorCode
- {
- InvalidXAML = 1,
- DuplicateXClass = 2,
- LegacyResmScheme = 3,
- TransformError = 4,
- EmitError = 4,
- Loader = 5,
-
- Unknown = 9999
- }
-}
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
index 3bd1ce5ce7..5287827652 100644
--- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -56,7 +56,9 @@ namespace Avalonia.Build.Tasks
refInput, RefOutputPath,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
- (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
+ new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
+ (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
+ SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
if (!res.Success)
{
WrittenFilePaths = writtenFilePaths.ToArray();
@@ -121,6 +123,10 @@ namespace Avalonia.Build.Tasks
public bool DebuggerLaunch { get; set; }
+ public bool VerboseExceptions { get; set; }
+
+ public ITaskItem[] AnalyzerConfigFiles { get; set; }
+
[Output]
public string[] WrittenFilePaths { get; private set; } = Array.Empty();
}
diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs
index b0b7e0cc3d..557e1e1fbe 100644
--- a/src/Avalonia.Build.Tasks/Extensions.cs
+++ b/src/Avalonia.Build.Tasks/Extensions.cs
@@ -1,41 +1,110 @@
using System;
+using System.Text;
+using System.Xml;
using Microsoft.Build.Framework;
+using XamlX;
-namespace Avalonia.Build.Tasks
+namespace Avalonia.Build.Tasks;
+
+internal static class Extensions
{
- static class Extensions
+ public static void LogError(this IBuildEngine engine, string code, string file, Exception ex,
+ int? lineNumber = null, int? linePosition = null)
{
- static string FormatErrorCode(BuildEngineErrorCode code) => FormattableString.Invariant($"AVLN:{(int)code:0000}");
-
- public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, Exception ex,
- int lineNumber = 0, int linePosition = 0)
+ if (lineNumber is null && linePosition is null
+ && ex is XmlException xe)
{
+ lineNumber = xe.LineNumber;
+ linePosition = xe.LinePosition;
+ }
+
#if DEBUG
- LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
+ LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
#else
- LogError(engine, code, file, ex.Message, lineNumber, linePosition);
+ LogError(engine, code, file, ex.Message, lineNumber, linePosition);
#endif
+ }
+
+ public static void LogDiagnostic(this IBuildEngine engine, XamlDiagnostic diagnostic)
+ {
+ var message = diagnostic.Title;
+
+ if (diagnostic.Severity == XamlDiagnosticSeverity.None)
+ {
+ // Skip.
}
-
- public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
- int lineNumber = 0, int linePosition = 0)
+ else if (diagnostic.Severity == XamlDiagnosticSeverity.Warning)
{
- engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
- lineNumber, linePosition, lineNumber, linePosition, message,
+ engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", diagnostic.Code, diagnostic.Document ?? "",
+ diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
+ diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
+ message,
"", "Avalonia"));
}
-
- public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
- int lineNumber = 0, int linePosition = 0)
+ else
{
- engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
- lineNumber, linePosition, lineNumber, linePosition, message,
+ engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", diagnostic.Code, diagnostic.Document ?? "",
+ diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
+ diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
+ message,
"", "Avalonia"));
}
+ }
+
+ public static void LogError(this IBuildEngine engine, string code, string file, string message,
+ int? lineNumber = null, int? linePosition = null)
+ {
+ engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", code, file ?? "",
+ lineNumber ?? 0, linePosition ?? 0, lineNumber ?? 0, linePosition ?? 0,
+ message, "", "Avalonia"));
+ }
- public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
+ public static void LogWarning(this IBuildEngine engine, string code, string file, string message,
+ int lineNumber = 0, int linePosition = 0)
+ {
+ engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", code, file ?? "",
+ lineNumber, linePosition, lineNumber, linePosition, message,
+ "", "Avalonia"));
+ }
+
+ public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
+ {
+ engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
+ }
+
+ public static string FormatException(this Exception exception, bool verbose)
+ {
+ if (!verbose)
{
- engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
+ return exception.Message;
+ }
+
+ var builder = new StringBuilder();
+ Process(exception);
+ return builder.ToString();
+
+ // Inspired by https://github.com/dotnet/msbuild/blob/e6409007d3a09255431eb28af01835ce1cd316b5/src/Shared/TaskLoggingHelper.cs#L909
+ void Process(Exception exception)
+ {
+ if (exception is AggregateException aggregateException)
+ {
+ foreach (Exception innerException in aggregateException.Flatten().InnerExceptions)
+ {
+ Process(innerException);
+ }
+
+ return;
+ }
+
+ do
+ {
+ builder.Append(exception.GetType().Name);
+ builder.Append(": ");
+ builder.AppendLine(exception.Message);
+ builder.AppendLine(exception.StackTrace);
+
+ exception = exception.InnerException;
+ } while (exception != null);
}
}
}
diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
index 86c04132a0..6aa815a28b 100644
--- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
+++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Avalonia.Markup.Xaml.PortableXaml;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Utilities;
using Microsoft.Build.Framework;
using SPath = System.IO.Path;
@@ -100,7 +101,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception e)
{
- BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
+ BuildEngine.LogError(AvaloniaXamlDiagnosticCodes.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
return false;
}
@@ -109,7 +110,7 @@ namespace Avalonia.Build.Tasks
if (typeToXamlIndex.ContainsKey(info.XClass))
{
- BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath,
+ BuildEngine.LogError(AvaloniaXamlDiagnosticCodes.DuplicateXClass, s.SystemPath,
$"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}");
return false;
}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs b/src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs
new file mode 100644
index 0000000000..4314c7b724
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Microsoft.Build.Framework;
+using XamlX;
+
+namespace Avalonia.Build.Tasks;
+
+// With MSBuild, we don't need to read for TreatWarningsAsErrors/WarningsAsErrors/WarningsNotAsErrors/NoWarn properties.
+// Just by reporting them with LogWarning MSBuild will do the rest for us.
+// But we still need to read EditorConfig manually.
+public class XamlCompilerDiagnosticsFilter
+{
+ private static readonly Regex s_editorConfigRegex =
+ new("""avalonia_xaml_diagnostic\.([\w\d]+)\.severity\s*=\s*(\w*)""");
+
+ private readonly Lazy> _lazyEditorConfig;
+
+ public XamlCompilerDiagnosticsFilter(
+ ITaskItem[]? analyzerConfigFiles)
+ {
+ _lazyEditorConfig = new Lazy>(() => ParseEditorConfigFiles(analyzerConfigFiles));
+ }
+
+ internal XamlDiagnosticSeverity Handle(XamlDiagnostic diagnostic)
+ {
+ return Handle(diagnostic.Severity, diagnostic.Code);
+ }
+
+ internal XamlDiagnosticSeverity Handle(XamlDiagnosticSeverity currentSeverity, string diagnosticCode)
+ {
+ if (_lazyEditorConfig.Value.TryGetValue(diagnosticCode, out var severity))
+ {
+ return severity.ToLowerInvariant() switch
+ {
+ "default" => currentSeverity,
+ "error" => XamlDiagnosticSeverity.Error,
+ "warning" => XamlDiagnosticSeverity.Warning,
+ _ => XamlDiagnosticSeverity.None // "suggestion", "silent", "none"
+ };
+ }
+
+ return currentSeverity;
+ }
+
+ private Dictionary ParseEditorConfigFiles(ITaskItem[]? analyzerConfigFiles)
+ {
+ // Very naive EditorConfig parser, supporting minimal properties set via regex:
+ var severities = new Dictionary();
+ if (analyzerConfigFiles is not null)
+ {
+ foreach (var fileItem in analyzerConfigFiles)
+ {
+ if (File.Exists(fileItem.ItemSpec))
+ {
+ var fileContent = File.ReadAllText(fileItem.ItemSpec);
+ var matches = s_editorConfigRegex.Matches(fileContent);
+ foreach (Match match in matches)
+ {
+ severities[match.Groups[1].Value] = match.Groups[2].Value;
+ }
+ }
+ }
+ }
+
+ return severities;
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 3f68092b56..4a5b8b7ab2 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -44,21 +45,13 @@ namespace Avalonia.Build.Tasks
}
}
- public static CompileResult Compile(IBuildEngine engine,
- string input, string output,
- string refInput, string refOutput,
- string[] references, string projectDirectory,
- bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
- bool skipXamlCompilation)
- {
- return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
- }
-
internal static CompileResult Compile(IBuildEngine engine,
string input, string output,
string refInput, string refOutput,
string[] references, string projectDirectory,
- bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
+ bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance,
+ XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey,
+ bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions)
{
try
{
@@ -70,7 +63,10 @@ namespace Avalonia.Build.Tasks
var refAsm = refTypeSystem?.TargetAssemblyDefinition;
if (!skipXamlCompilation)
{
- var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch);
+ var compileRes = CompileCore(
+ engine, typeSystem, projectDirectory, verifyIl,
+ defaultCompileBindings, logImportance, diagnosticsFilter,
+ debuggerLaunch, verboseExceptions);
if (compileRes == null)
return new CompileResult(true);
if (compileRes == false)
@@ -99,7 +95,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception ex)
{
- engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
+ engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, "", ex);
return new CompileResult(false);
}
}
@@ -107,8 +103,10 @@ namespace Avalonia.Build.Tasks
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
string projectDirectory, bool verifyIl,
bool defaultCompileBindings,
- MessageImportance logImportance
- , bool debuggerLaunch = false)
+ MessageImportance logImportance,
+ XamlCompilerDiagnosticsFilter diagnosticsFilter,
+ bool debuggerLaunch,
+ bool verboseExceptions)
{
if (debuggerLaunch)
{
@@ -170,6 +168,20 @@ namespace Avalonia.Build.Tasks
asm.MainModule.Types.Add(trampolineBuilder);
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
+ var diagnostics = new List();
+ var diagnosticsHandler = new XamlDiagnosticsHandler()
+ {
+ HandleDiagnostic = diagnostic =>
+ {
+ var newSeverity = diagnosticsFilter.Handle(diagnostic);
+ diagnostic = diagnostic with { Severity = newSeverity };
+ diagnostics.Add(diagnostic);
+ return newSeverity;
+ },
+ CodeMappings = AvaloniaXamlDiagnosticCodes.XamlXDiagnosticCodeToAvalonia,
+ ExceptionFormatter = ex => ex.FormatException(verboseExceptions)
+ };
+
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
@@ -178,7 +190,8 @@ namespace Avalonia.Build.Tasks
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
- new DeterministicIdGenerator());
+ new DeterministicIdGenerator(),
+ diagnosticsHandler);
var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext",
@@ -259,6 +272,7 @@ namespace Avalonia.Build.Tasks
{
var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
+ var transformFailed = false;
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{
@@ -277,6 +291,7 @@ namespace Avalonia.Build.Tasks
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlParser.Parse(xaml);
+ parsed.Document = res.FilePath;
var initialRoot = (XamlAstObjectNode)parsed.Root;
@@ -338,8 +353,7 @@ namespace Avalonia.Build.Tasks
new XamlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}
-
-
+
compiler.Transform(parsed);
var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
@@ -376,29 +390,28 @@ namespace Avalonia.Build.Tasks
}
catch (Exception e)
{
- int lineNumber = 0, linePosition = 0;
- if (e is XamlParseException xe)
- {
- lineNumber = xe.LineNumber;
- linePosition = xe.LinePosition;
- }
-
- engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition);
- return false;
+ transformFailed = true;
+ engine.LogError(AvaloniaXamlDiagnosticCodes.TransformError, res.FilePath, e);
}
}
try
{
- compiler.TransformGroup(parsedXamlDocuments);
+ if (!transformFailed)
+ {
+ compiler.TransformGroup(parsedXamlDocuments);
+ }
}
- catch (XamlDocumentParseException e)
+ catch (Exception e)
{
- engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition);
+ transformFailed = true;
+ engine.LogError(AvaloniaXamlDiagnosticCodes.TransformError, "", e);
}
- catch (XamlParseException e)
+
+ var hasAnyError = ReportDiagnostics(engine, diagnostics) || transformFailed;
+ if (hasAnyError)
{
- engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition);
+ return false;
}
foreach (var document in parsedXamlDocuments)
@@ -605,21 +618,23 @@ namespace Avalonia.Build.Tasks
}
else
{
- engine.LogWarning(BuildEngineErrorCode.Loader, "",
- $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found");
+ var diagnostic = new XamlDiagnostic(
+ AvaloniaXamlDiagnosticCodes.XamlLoaderUnreachable,
+ diagnosticsFilter.Handle(
+ XamlDiagnosticSeverity.Warning,
+ AvaloniaXamlDiagnosticCodes.XamlLoaderUnreachable),
+ $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found")
+ {
+ Document = document.FileSource?.FilePath
+ };
+ engine.LogDiagnostic(diagnostic);
}
}
}
catch (Exception e)
{
- int lineNumber = 0, linePosition = 0;
- if (e is XamlParseException xe)
- {
- lineNumber = xe.LineNumber;
- linePosition = xe.LinePosition;
- }
- engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition);
+ engine.LogError(AvaloniaXamlDiagnosticCodes.EmitError, res.FilePath, e);
return false;
}
res.Remove();
@@ -636,7 +651,6 @@ namespace Avalonia.Build.Tasks
}
}
-
return true;
}
@@ -652,6 +666,21 @@ namespace Avalonia.Build.Tasks
return true;
}
+ static bool ReportDiagnostics(IBuildEngine engine, IReadOnlyCollection diagnostics)
+ {
+ var hasAnyError = diagnostics.Any(d => d.Severity >= XamlDiagnosticSeverity.Error);
+
+ const int maxErrorsPerDocument = 100;
+ foreach (var diagnostic in diagnostics
+ .GroupBy(d => d.Document)
+ .SelectMany(d => d.Take(maxErrorsPerDocument)))
+ {
+ engine.LogDiagnostic(diagnostic);
+ }
+
+ return hasAnyError;
+ }
+
static bool? CompileCoreForRefAssembly(
IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
{
@@ -693,9 +722,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception e)
{
- engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "",
- 0, 0, 0, 0,
- e.Message, "", "Avalonia"));
+ engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, e.Message, e);
return false;
}
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
index f051d9bbab..b74e4e59aa 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
@@ -1,9 +1,11 @@
-
+ Top
+
@@ -51,7 +53,7 @@
+ Placement="{DynamicResource ColorPickerFlyoutPlacement}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/ControlHighlightAdorner.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/ControlHighlightAdorner.cs
new file mode 100644
index 0000000000..56c933cfbd
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/ControlHighlightAdorner.cs
@@ -0,0 +1,47 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Media;
+using Avalonia.Reactive;
+
+namespace Avalonia.Diagnostics.Controls;
+
+internal class ControlHighlightAdorner : Control
+{
+
+ readonly IPen _pen;
+
+ private ControlHighlightAdorner(IPen pen)
+ {
+ _pen = pen;
+ this.Clip = null;
+ }
+
+ public static IDisposable? Add(InputElement owner, IBrush highlightBrush)
+ {
+
+ if (AdornerLayer.GetAdornerLayer(owner) is { } layer)
+ {
+ var pen = new Pen(highlightBrush, 2).ToImmutable();
+ var adorner = new ControlHighlightAdorner(pen)
+ {
+ [AdornerLayer.AdornedElementProperty] = owner
+ };
+ layer.Children.Add(adorner);
+
+ return Disposable.Create((layer, adorner), state =>
+ {
+ state.layer.Children.Remove(state.adorner);
+ });
+ }
+ return default;
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ base.Render(context);
+ context.DrawRectangle(_pen, Bounds.Deflate(2));
+ }
+
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToImageConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToImageConverter.cs
new file mode 100644
index 0000000000..6f6c4b977d
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToImageConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace Avalonia.Diagnostics.Converters;
+
+internal class BoolToImageConverter : IValueConverter
+{
+ public IImage? TrueImage { get; set; }
+
+ public IImage? FalseImage { get; set; }
+
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
+ value switch
+ {
+ true => TrueImage,
+ false => FalseImage,
+ _ => null
+ };
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
+ BindingOperations.DoNothing;
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BrushSelectorConveter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BrushSelectorConveter.cs
new file mode 100644
index 0000000000..2216b0e9fc
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/BrushSelectorConveter.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace Avalonia.Diagnostics.Converters;
+
+internal class BrushSelectorConveter : AvaloniaObject, IValueConverter
+{
+ public static readonly DirectProperty BrushProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Brush)
+ , o => o.Brush
+ , (o, v) => o.Brush = v);
+
+ public IBrush? Brush { get; set; }
+
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (ReferenceEquals(value, parameter))
+ {
+ return Brush;
+ }
+ else if (value is ISolidColorBrush a
+ && parameter is ISolidColorBrush b
+ && a.Color == b.Color
+ && a.Transform == b.Transform
+ && b.Opacity == a.Opacity
+ )
+ {
+ return Brush;
+ }
+ return null;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return BindingOperations.DoNothing;
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index 3cfb0246eb..909c2baa9c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -1,5 +1,5 @@
-using System;
-using Avalonia.Input;
+using Avalonia.Input;
+using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Diagnostics
@@ -47,5 +47,10 @@ namespace Avalonia.Diagnostics
/// Gets or sets whether DevTools theme.
///
public ThemeVariant? ThemeVariant { get; set; }
+
+ ///
+ /// Get or set Focus Highlighter
+ ///
+ public IBrush? FocusHighlighterBrush { get; set; }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
index 2412ea5325..d8f69ef6e5 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using Avalonia.Data;
namespace Avalonia.Diagnostics.ViewModels
@@ -49,7 +50,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public override string Group => _group;
+ public override string Group => IsPinned ? "Pinned" : _group;
public override Type? DeclaringType { get; }
public override Type PropertyType => _propertyType;
@@ -114,5 +115,14 @@ namespace Avalonia.Diagnostics.ViewModels
}
RaisePropertyChanged(nameof(Type));
}
+
+ protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ base.OnPropertyChanged(e);
+ if (e.PropertyName == nameof(IsPinned))
+ {
+ RaisePropertyChanged(nameof(Group));
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
index b7ee1459f7..60959c7d25 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Reflection;
namespace Avalonia.Diagnostics.ViewModels
@@ -36,7 +37,7 @@ namespace Avalonia.Diagnostics.ViewModels
public PropertyInfo Property { get; }
public override object Key => Name;
public override string Name { get; }
- public override string Group => "CLR Properties";
+ public override string Group => IsPinned ? "Pinned" : "CLR Properties";
public override Type AssignedType => _assignedType;
public override Type PropertyType => _propertyType;
@@ -82,5 +83,14 @@ namespace Avalonia.Diagnostics.ViewModels
RaiseAndSetIfChanged(ref _assignedType, valueType ?? Property.PropertyType, nameof(AssignedType));
RaisePropertyChanged(nameof(Type));
}
+
+ protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ base.OnPropertyChanged(e);
+ if (e.PropertyName == nameof(IsPinned))
+ {
+ RaisePropertyChanged(nameof(Group));
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index fc26d47c78..d57d3088f6 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
@@ -10,13 +11,13 @@ using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Styling;
-using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
internal class ControlDetailsViewModel : ViewModelBase, IDisposable, IClassesChangedListener
{
private readonly AvaloniaObject _avaloniaObject;
+ private readonly ISet _pinnedProperties;
private IDictionary? _propertyIndex;
private PropertyViewModel? _selectedProperty;
private DataGridCollectionView? _propertiesView;
@@ -24,19 +25,29 @@ namespace Avalonia.Diagnostics.ViewModels
private bool _showInactiveStyles;
private string? _styleStatus;
private object? _selectedEntity;
- private readonly Stack<(string Name,object Entry)> _selectedEntitiesStack = new();
+ private readonly Stack<(string Name, object Entry)> _selectedEntitiesStack = new();
private string? _selectedEntityName;
private string? _selectedEntityType;
private bool _showImplementedInterfaces;
+ // new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))
+ private readonly static IReadOnlyList GroupDescriptors = new DataGridPathGroupDescription[]
+ {
+ new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))
+ };
- public ControlDetailsViewModel(TreePageViewModel treePage, AvaloniaObject avaloniaObject)
+ private readonly static IReadOnlyList SortDescriptions = new DataGridSortDescription[]
{
- _avaloniaObject = avaloniaObject;
+ new DataGridComparerSortDescription(PropertyComparer.Instance!, ListSortDirection.Ascending),
+ };
+ public ControlDetailsViewModel(TreePageViewModel treePage, AvaloniaObject avaloniaObject, ISet pinnedProperties)
+ {
+ _avaloniaObject = avaloniaObject;
+ _pinnedProperties = pinnedProperties;
TreePage = treePage;
- Layout = avaloniaObject is Visual visual
- ? new ControlLayoutViewModel(visual)
- : default;
+ Layout = avaloniaObject is Visual visual
+ ? new ControlLayoutViewModel(visual)
+ : default;
NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString());
@@ -84,7 +95,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
var setterValue = regularSetter.Value;
- var resourceInfo = GetResourceInfo(setterValue);
+ var resourceInfo = GetResourceInfo(setterValue);
SetterViewModel setterVm;
@@ -175,13 +186,13 @@ namespace Avalonia.Diagnostics.ViewModels
get => _selectedEntityName;
set => RaiseAndSetIfChanged(ref _selectedEntityName, value);
}
-
+
public string? SelectedEntityType
{
get => _selectedEntityType;
set => RaiseAndSetIfChanged(ref _selectedEntityType, value);
}
-
+
public PropertyViewModel? SelectedProperty
{
get => _selectedProperty;
@@ -395,14 +406,23 @@ namespace Avalonia.Diagnostics.ViewModels
return !(arg is PropertyViewModel property) || TreePage.PropertiesFilter.Filter(property.Name);
}
- private class PropertyComparer : IComparer
+ private class PropertyComparer : IComparer, IComparer
{
public static PropertyComparer Instance { get; } = new PropertyComparer();
public int Compare(PropertyViewModel? x, PropertyViewModel? y)
{
- var groupX = GroupIndex(x?.Group);
- var groupY = GroupIndex(y?.Group);
+ if (x is null && y is null)
+ return 0;
+
+ if (x is null && y is not null)
+ return -1;
+
+ if (x is not null && y is null)
+ return 1;
+
+ var groupX = GroupIndex(x!.Group);
+ var groupY = GroupIndex(y!.Group);
if (groupX != groupY)
{
@@ -410,7 +430,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
else
{
- return string.CompareOrdinal(x?.Name, y?.Name);
+ return string.CompareOrdinal(x.Name, y.Name);
}
}
@@ -418,12 +438,21 @@ namespace Avalonia.Diagnostics.ViewModels
{
switch (group)
{
- case "Properties": return 0;
- case "Attached Properties": return 1;
- case "CLR Properties": return 2;
- default: return 3;
+ case "Pinned":
+ return -1;
+ case "Properties":
+ return 0;
+ case "Attached Properties":
+ return 1;
+ case "CLR Properties":
+ return 2;
+ default:
+ return 3;
}
}
+
+ public int Compare(object? x, object? y) =>
+ Compare(x as PropertyViewModel, y as PropertyViewModel);
}
private static IEnumerable GetAllPublicProperties(Type type)
@@ -438,8 +467,8 @@ namespace Avalonia.Diagnostics.ViewModels
var selectedProperty = SelectedProperty;
var selectedEntity = SelectedEntity;
var selectedEntityName = SelectedEntityName;
- if (selectedEntity == null
- || selectedProperty == null
+ if (selectedEntity == null
+ || selectedProperty == null
|| selectedProperty.PropertyType == typeof(string)
|| selectedProperty.PropertyType.IsValueType)
return;
@@ -455,19 +484,19 @@ namespace Avalonia.Diagnostics.ViewModels
break;
case ClrPropertyViewModel clrProperty:
- {
- property = GetAllPublicProperties(selectedEntity.GetType())
- .FirstOrDefault(pi => clrProperty.Property == pi)?
- .GetValue(selectedEntity);
+ {
+ property = GetAllPublicProperties(selectedEntity.GetType())
+ .FirstOrDefault(pi => clrProperty.Property == pi)?
+ .GetValue(selectedEntity);
- break;
- }
+ break;
+ }
}
- if (property == null)
+ if (property == null)
return;
- _selectedEntitiesStack.Push((Name:selectedEntityName!, Entry:selectedEntity));
+ _selectedEntitiesStack.Push((Name: selectedEntityName!, Entry: selectedEntity));
var propertyName = selectedProperty.Name;
@@ -492,7 +521,7 @@ namespace Avalonia.Diagnostics.ViewModels
RaisePropertyChanged(nameof(CanNavigateToParentProperty));
}
}
-
+
protected void NavigateToProperty(object o, string? entityName)
{
var oldSelectedEntity = SelectedEntity;
@@ -514,17 +543,19 @@ namespace Avalonia.Diagnostics.ViewModels
var properties = GetAvaloniaProperties(o)
.Concat(GetClrProperties(o, _showImplementedInterfaces))
- .OrderBy(x => x, PropertyComparer.Instance)
- .ThenBy(x => x.Name)
+ .Do(p =>
+ {
+ p.IsPinned = _pinnedProperties.Contains(p.FullName);
+ })
.ToArray();
_propertyIndex = properties
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.ToArray());
-
var view = new DataGridCollectionView(properties);
- view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));
+ view.GroupDescriptions.AddRange(GroupDescriptors);
+ view.SortDescriptions.AddRange(SortDescriptions);
view.Filter = FilterProperty;
PropertiesView = view;
@@ -539,7 +570,7 @@ namespace Avalonia.Diagnostics.ViewModels
break;
}
}
-
+
internal void SelectProperty(AvaloniaProperty property)
{
SelectedProperty = null;
@@ -547,10 +578,10 @@ namespace Avalonia.Diagnostics.ViewModels
if (SelectedEntity != _avaloniaObject)
{
NavigateToProperty(
- _avaloniaObject,
- (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString());
+ _avaloniaObject,
+ (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString());
}
-
+
if (PropertiesView is null)
{
return;
@@ -561,7 +592,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (o is AvaloniaPropertyViewModel propertyVm && propertyVm.Property == property)
{
SelectedProperty = propertyVm;
-
+
break;
}
}
@@ -573,5 +604,24 @@ namespace Avalonia.Diagnostics.ViewModels
SelectedProperty = null;
NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString());
}
+
+ public void TogglePinnedProperty(object parameter)
+ {
+ if (parameter is PropertyViewModel model)
+ {
+ var fullname = model.FullName;
+ if (_pinnedProperties.Contains(fullname))
+ {
+ _pinnedProperties.Remove(fullname);
+ model.IsPinned = false;
+ }
+ else
+ {
+ _pinnedProperties.Add(fullname);
+ model.IsPinned = true;
+ }
+ PropertiesView?.Refresh();
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index f993d2c957..06e4e06f8a 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -8,6 +8,9 @@ using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.Reactive;
using Avalonia.Rendering;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Controls.Primitives;
namespace Avalonia.Diagnostics.ViewModels
{
@@ -27,14 +30,17 @@ namespace Avalonia.Diagnostics.ViewModels
private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot;
private IScreenshotHandler? _screenshotHandler;
- private bool _showPropertyType;
+ private bool _showPropertyType;
private bool _showImplementedInterfaces;
-
+ private readonly HashSet _pinnedProperties = new();
+ private IBrush? _FocusHighlighter;
+ private IDisposable? _currentFocusHighlightAdorner = default;
+
public MainViewModel(AvaloniaObject root)
{
_root = root;
- _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
- _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root));
+ _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root), _pinnedProperties);
+ _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root), _pinnedProperties);
_events = new EventsPageViewModel(this);
UpdateFocusedControl();
@@ -208,10 +214,10 @@ namespace Avalonia.Diagnostics.ViewModels
private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
}
- public IInputRoot? PointerOverRoot
- {
+ public IInputRoot? PointerOverRoot
+ {
get => _pointerOverRoot;
- private set => RaiseAndSetIfChanged( ref _pointerOverRoot , value);
+ private set => RaiseAndSetIfChanged(ref _pointerOverRoot, value);
}
public IInputElement? PointerOverElement
@@ -262,7 +268,7 @@ namespace Avalonia.Diagnostics.ViewModels
_pointerOverSubscription.Dispose();
_logicalTree.Dispose();
_visualTree.Dispose();
-
+ _currentFocusHighlightAdorner?.Dispose();
if (TryGetRenderer() is { } renderer)
{
renderer.Diagnostics.DebugOverlays = RendererDebugOverlays.None;
@@ -271,7 +277,20 @@ namespace Avalonia.Diagnostics.ViewModels
private void UpdateFocusedControl()
{
- FocusedControl = KeyboardDevice.Instance?.FocusedElement?.GetType().Name;
+ var element = KeyboardDevice.Instance?.FocusedElement;
+ FocusedControl = element?.GetType().Name;
+ _currentFocusHighlightAdorner?.Dispose();
+ if (FocusHighlighter is IBrush brush
+ && element is InputElement input
+ && TopLevel.GetTopLevel(input) is { } topLevel
+ && (topLevel is not Views.MainWindow))
+ {
+ if (topLevel is PopupRoot pr && pr.ParentTopLevel is Views.MainWindow)
+ {
+ return;
+ }
+ _currentFocusHighlightAdorner = Controls.ControlHighlightAdorner.Add(input, brush);
+ }
}
private void KeyboardPropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -297,7 +316,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
public int? StartupScreenIndex { get; private set; } = default;
-
+
[DependsOn(nameof(TreePageViewModel.SelectedNode))]
[DependsOn(nameof(Content))]
public bool CanShot(object? parameter)
@@ -331,12 +350,13 @@ namespace Avalonia.Diagnostics.ViewModels
_screenshotHandler = options.ScreenshotHandler;
StartupScreenIndex = options.StartupScreenIndex;
ShowImplementedInterfaces = options.ShowImplementedInterfaces;
+ FocusHighlighter = options.FocusHighlighterBrush;
}
- public bool ShowImplementedInterfaces
- {
- get => _showImplementedInterfaces;
- private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces , value);
+ public bool ShowImplementedInterfaces
+ {
+ get => _showImplementedInterfaces;
+ private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces, value);
}
public void ToggleShowImplementedInterfaces(object parameter)
@@ -349,14 +369,25 @@ namespace Avalonia.Diagnostics.ViewModels
}
public bool ShowDetailsPropertyType
- {
- get => _showPropertyType;
- private set => RaiseAndSetIfChanged(ref _showPropertyType , value);
+ {
+ get => _showPropertyType;
+ private set => RaiseAndSetIfChanged(ref _showPropertyType, value);
}
public void ToggleShowDetailsPropertyType(object parameter)
{
ShowDetailsPropertyType = !ShowDetailsPropertyType;
}
+
+ public IBrush? FocusHighlighter
+ {
+ get => _FocusHighlighter;
+ private set => RaiseAndSetIfChanged(ref _FocusHighlighter, value);
+ }
+
+ public void SelectFocusHighlighter(object parameter)
+ {
+ FocusHighlighter = parameter as IBrush;
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
index aa2682e376..f81b34c1bb 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
@@ -1,27 +1,29 @@
using System;
-using System.ComponentModel;
-using System.Globalization;
-using System.Reflection;
-namespace Avalonia.Diagnostics.ViewModels
+namespace Avalonia.Diagnostics.ViewModels;
+
+internal abstract class PropertyViewModel : ViewModelBase
{
- internal abstract class PropertyViewModel : ViewModelBase
- {
- public abstract object Key { get; }
- public abstract string Name { get; }
- public abstract string Group { get; }
- public abstract Type AssignedType { get; }
- public abstract Type? DeclaringType { get; }
- public abstract object? Value { get; set; }
- public abstract string Priority { get; }
- public abstract bool? IsAttached { get; }
- public abstract void Update();
- public abstract Type PropertyType { get; }
+ private bool _isPinned;
+
+ public abstract object Key { get; }
+ public abstract string Name { get; }
+ public abstract string Group { get; }
+ public abstract Type AssignedType { get; }
+ public abstract Type? DeclaringType { get; }
+ public abstract object? Value { get; set; }
+ public abstract string Priority { get; }
+ public abstract bool? IsAttached { get; }
+ public abstract void Update();
+ public abstract Type PropertyType { get; }
+
+ public string Type => PropertyType == AssignedType ?
+ PropertyType.GetTypeName() :
+ $"{PropertyType.GetTypeName()} {{{AssignedType.GetTypeName()}}}";
+
+ public abstract bool IsReadonly { get; }
- public string Type => PropertyType == AssignedType ?
- PropertyType.GetTypeName() :
- $"{PropertyType.GetTypeName()} {{{AssignedType.GetTypeName()}}}";
+ public bool IsPinned { get => _isPinned; set => RaiseAndSetIfChanged(ref _isPinned, value); }
- public abstract bool IsReadonly { get; }
- }
+ public string FullName => $"{GetType().Name.Replace("PropertyViewModel","")}:{DeclaringType?.FullName}.{Name}";
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
index 67dbfed92b..f6fe121f28 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
@@ -2,10 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.LogicalTree;
-using Avalonia.Metadata;
-using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
@@ -14,12 +10,13 @@ namespace Avalonia.Diagnostics.ViewModels
{
private TreeNode? _selectedNode;
private ControlDetailsViewModel? _details;
+ private readonly ISet _pinnedProperties;
- public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
+ public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes, ISet pinnedProperties)
{
MainView = mainView;
Nodes = nodes;
-
+ _pinnedProperties = pinnedProperties;
PropertiesFilter = new FilterViewModel();
PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView?.Refresh();
@@ -45,7 +42,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (RaiseAndSetIfChanged(ref _selectedNode, value))
{
Details = value != null ?
- new ControlDetailsViewModel(this, value.Visual) :
+ new ControlDetailsViewModel(this, value.Visual, _pinnedProperties) :
null;
Details?.UpdatePropertiesView(MainView.ShowImplementedInterfaces);
Details?.UpdateStyleFilters();
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
index 63b002d110..f62c0f2a72 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -10,10 +10,23 @@
x:DataType="vm:ControlDetailsViewModel">
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -59,7 +72,38 @@
CanUserResizeColumns="true"
DoubleTapped="PropertiesGrid_OnDoubleTapped">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index eac807a5bc..f810d8450c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -2,8 +2,24 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="using:Avalonia.Diagnostics.Views"
xmlns:viewModels="using:Avalonia.Diagnostics.ViewModels"
+ xmlns:convertes="using:Avalonia.Diagnostics.Converters"
x:Class="Avalonia.Diagnostics.Views.MainView"
x:DataType="viewModels:MainViewModel">
+
+ 16
+
+
+
+
+
diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
index ba432337dc..ee52496866 100644
--- a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
+++ b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Dialogs
{
public class AboutAvaloniaDialog : Window
{
- private static readonly Version s_version = typeof(AboutAvaloniaDialog).Assembly.GetName().Version;
+ private static readonly Version s_version = typeof(AboutAvaloniaDialog).Assembly.GetName().Version!;
public static string Version { get; } = $@"v{s_version.ToString(2)}";
@@ -45,7 +45,7 @@ namespace Avalonia.Dialogs
{
if (waitForExit)
{
- process.WaitForExit();
+ process?.WaitForExit();
}
}
}
@@ -61,7 +61,7 @@ namespace Avalonia.Dialogs
}
else
{
- using Process process = Process.Start(new ProcessStartInfo
+ using Process? process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "",
diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
index 1b0efed66f..61f47aac4a 100644
--- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
+++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
@@ -21,4 +21,5 @@
+
diff --git a/src/Avalonia.Dialogs/Internal/AvaloniaDialogsInternalViewModelBase.cs b/src/Avalonia.Dialogs/Internal/AvaloniaDialogsInternalViewModelBase.cs
index 474f8ebd58..386b3ccfd9 100644
--- a/src/Avalonia.Dialogs/Internal/AvaloniaDialogsInternalViewModelBase.cs
+++ b/src/Avalonia.Dialogs/Internal/AvaloniaDialogsInternalViewModelBase.cs
@@ -1,14 +1,15 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
+using Avalonia.Metadata;
namespace Avalonia.Dialogs.Internal
{
public class AvaloniaDialogsInternalViewModelBase : INotifyPropertyChanged
{
- public event PropertyChangedEventHandler PropertyChanged;
+ public event PropertyChangedEventHandler? PropertyChanged;
- internal protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null)
+ internal protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (!EqualityComparer.Default.Equals(field, value))
{
@@ -20,7 +21,7 @@ namespace Avalonia.Dialogs.Internal
return false;
}
- internal protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ internal protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
diff --git a/src/Avalonia.Dialogs/Internal/BclMountedVolumeInfoProvider.cs b/src/Avalonia.Dialogs/Internal/BclMountedVolumeInfoProvider.cs
new file mode 100644
index 0000000000..6dbd7f3a16
--- /dev/null
+++ b/src/Avalonia.Dialogs/Internal/BclMountedVolumeInfoProvider.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using Avalonia.Controls.Platform;
+using Avalonia.Reactive;
+
+namespace Avalonia.Dialogs.Internal;
+
+internal class BclMountedVolumeInfoProvider : IMountedVolumeInfoProvider
+{
+ public IDisposable Listen(ObservableCollection mountedDrives)
+ {
+ foreach (var drive in DriveInfo.GetDrives())
+ {
+ string directory;
+ ulong totalSize;
+ try
+ {
+ if (!drive.IsReady)
+ continue;
+ totalSize = (ulong)drive.TotalSize;
+ directory = drive.RootDirectory.FullName;
+
+ _ = new DirectoryInfo(directory).EnumerateFileSystemInfos();
+ }
+ catch
+ {
+ continue;
+ }
+
+ mountedDrives.Add(new MountedVolumeInfo
+ {
+ VolumeLabel = string.IsNullOrEmpty(drive.VolumeLabel.Trim()) ? directory : drive.VolumeLabel,
+ VolumePath = directory,
+ VolumeSizeBytes = totalSize
+ });
+ }
+ return Disposable.Empty;
+ }
+}
diff --git a/src/Avalonia.Dialogs/Internal/ChildFitter.cs b/src/Avalonia.Dialogs/Internal/ChildFitter.cs
index 10cc106d33..1d23777048 100644
--- a/src/Avalonia.Dialogs/Internal/ChildFitter.cs
+++ b/src/Avalonia.Dialogs/Internal/ChildFitter.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Dialogs.Internal
protected override Size ArrangeOverride(Size finalSize)
{
- Child.Measure(finalSize);
+ Child?.Measure(finalSize);
base.ArrangeOverride(finalSize);
return finalSize;
}
diff --git a/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs
index 0060c5e8b2..a5862d8c70 100644
--- a/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs
+++ b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Dialogs.Internal
{
public class FileSizeStringConverter : IValueConverter
{
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is long size && size > 0)
{
@@ -16,7 +16,7 @@ namespace Avalonia.Dialogs.Internal
return "";
}
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
index 8e24da97a4..85b036ced1 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Dialogs.Internal
{
public class ManagedFileChooserFilterViewModel : AvaloniaDialogsInternalViewModelBase
{
- private readonly Regex[] _patterns;
+ private readonly Regex[]? _patterns;
public string Name { get; }
public ManagedFileChooserFilterViewModel(FilePickerFileType filter)
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
index b3fb7f7126..bf1bdc3d28 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
@@ -4,20 +4,20 @@ namespace Avalonia.Dialogs.Internal
{
public class ManagedFileChooserItemViewModel : AvaloniaDialogsInternalViewModelBase
{
- private string _displayName;
- private string _path;
+ private string? _displayName;
+ private string? _path;
private DateTime _modified;
- private string _type;
+ private string? _type;
private long _size;
private ManagedFileChooserItemType _itemType;
- public string DisplayName
+ public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
- public string Path
+ public string? Path
{
get => _path;
set => this.RaiseAndSetIfChanged(ref _path, value);
@@ -29,7 +29,7 @@ namespace Avalonia.Dialogs.Internal
set => this.RaiseAndSetIfChanged(ref _modified, value);
}
- public string Type
+ public string? Type
{
get => _type;
set => this.RaiseAndSetIfChanged(ref _type, value);
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs
index 6c578367fb..f3b775f163 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs
@@ -2,8 +2,8 @@
{
public class ManagedFileChooserNavigationItem
{
- public string DisplayName { get; set; }
- public string Path { get; set; }
+ public string? DisplayName { get; set; }
+ public string? Path { get; set; }
public ManagedFileChooserItemType ItemType { get; set; }
}
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
index 80e9c561fa..1b2d490007 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Dialogs.Internal
try
{
- Directory.GetFiles(x.VolumePath);
+ Directory.GetFiles(x.VolumePath!);
}
catch (Exception)
{
@@ -79,7 +79,7 @@ namespace Avalonia.Dialogs.Internal
};
})
.Where(x => x != null)
- .ToArray();
+ .ToArray()!;
}
}
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
index 0cd86511ed..ce2b8f2a49 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
@@ -15,9 +15,9 @@ namespace Avalonia.Dialogs.Internal
public class ManagedFileChooserViewModel : AvaloniaDialogsInternalViewModelBase
{
private readonly ManagedFileDialogOptions _options;
- public event Action CancelRequested;
- public event Action CompleteRequested;
- public event Action OverwritePrompt;
+ public event Action? CancelRequested;
+ public event Action? CompleteRequested;
+ public event Action? OverwritePrompt;
public AvaloniaList QuickLinks { get; } =
new AvaloniaList();
@@ -31,25 +31,25 @@ namespace Avalonia.Dialogs.Internal
public AvaloniaList SelectedItems { get; } =
new AvaloniaList();
- string _location;
- string _fileName;
+ string? _location;
+ string? _fileName;
private bool _showHiddenFiles;
- private ManagedFileChooserFilterViewModel _selectedFilter;
+ private ManagedFileChooserFilterViewModel? _selectedFilter;
private readonly bool _selectingDirectory;
private readonly bool _savingFile;
private bool _scheduledSelectionValidation;
private bool _alreadyCancelled = false;
- private string _defaultExtension;
+ private string? _defaultExtension;
private readonly bool _overwritePrompt;
private CompositeDisposable _disposables;
- public string Location
+ public string? Location
{
get => _location;
set => this.RaiseAndSetIfChanged(ref _location, value);
}
- public string FileName
+ public string? FileName
{
get => _fileName;
set => this.RaiseAndSetIfChanged(ref _fileName, value);
@@ -59,7 +59,7 @@ namespace Avalonia.Dialogs.Internal
public bool ShowFilters { get; }
public SelectionMode SelectionMode { get; }
- public string Title { get; }
+ public string? Title { get; }
public int QuickLinksSelectedIndex
{
@@ -80,7 +80,7 @@ namespace Avalonia.Dialogs.Internal
set => this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
}
- public ManagedFileChooserFilterViewModel SelectedFilter
+ public ManagedFileChooserFilterViewModel? SelectedFilter
{
get => _selectedFilter;
set
@@ -115,9 +115,9 @@ namespace Avalonia.Dialogs.Internal
.GetService()
?? new ManagedFileChooserSources();
- var sub1 = AvaloniaLocator.Current
- .GetRequiredService()
- .Listen(ManagedFileChooserSources.MountedVolumes);
+ var sub1 = (AvaloniaLocator.Current.GetService()
+ ?? new BclMountedVolumeInfoProvider())
+ .Listen(ManagedFileChooserSources.MountedVolumes);
var sub2 = ManagedFileChooserSources.MountedVolumes.GetWeakCollectionChangedObservable()
.Subscribe(x => Dispatcher.UIThread.Post(() => RefreshQuickLinks(quickSources)));
@@ -200,7 +200,7 @@ namespace Avalonia.Dialogs.Internal
}
}
- private async void OnSelectionChangedAsync(object sender, NotifyCollectionChangedEventArgs e)
+ private async void OnSelectionChangedAsync(object? sender, NotifyCollectionChangedEventArgs e)
{
if (_scheduledSelectionValidation)
{
@@ -244,11 +244,11 @@ namespace Avalonia.Dialogs.Internal
});
}
- void NavigateRoot(string initialSelectionName)
+ void NavigateRoot(string? initialSelectionName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName);
+ Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System))!, initialSelectionName);
}
else
{
@@ -258,14 +258,14 @@ namespace Avalonia.Dialogs.Internal
public void Refresh() => Navigate(Location);
- public void Navigate(IStorageFolder path, string initialSelectionName = null)
+ public void Navigate(IStorageFolder? path, string? initialSelectionName = null)
{
var fullDirectoryPath = path?.TryGetLocalPath() ?? Directory.GetCurrentDirectory();
Navigate(fullDirectoryPath, initialSelectionName);
}
- public void Navigate(string path, string initialSelectionName = null)
+ public void Navigate(string? path, string? initialSelectionName = null)
{
if (!Directory.Exists(path))
{
@@ -370,7 +370,7 @@ namespace Avalonia.Dialogs.Internal
{
if (_selectingDirectory)
{
- CompleteRequested?.Invoke(new[] { Location });
+ CompleteRequested?.Invoke(new[] { Location! });
}
else if (_savingFile)
{
@@ -381,7 +381,7 @@ namespace Avalonia.Dialogs.Internal
FileName = Path.ChangeExtension(FileName, _defaultExtension);
}
- var fullName = Path.Combine(Location, FileName);
+ var fullName = Path.Combine(Location!, FileName);
if (_overwritePrompt && File.Exists(fullName))
{
@@ -395,13 +395,13 @@ namespace Avalonia.Dialogs.Internal
}
else
{
- CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray());
+ CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path!).ToArray());
}
}
public void SelectSingleFile(ManagedFileChooserItemViewModel item)
{
- CompleteRequested?.Invoke(new[] { item.Path });
+ CompleteRequested?.Invoke(new[] { item.Path! });
}
}
}
diff --git a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
index e46b9276fc..4613499d9d 100644
--- a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
+++ b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
@@ -7,13 +7,13 @@ namespace Avalonia.Dialogs.Internal
{
public class ResourceSelectorConverter : ResourceDictionary, IValueConverter
{
- public object Convert(object key, Type targetType, object parameter, CultureInfo culture)
+ public object? Convert(object? key, Type targetType, object? parameter, CultureInfo culture)
{
- TryGetResource((string)key, null, out var value);
+ TryGetResource((string)key!, null, out var value);
return value;
}
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.cs b/src/Avalonia.Dialogs/ManagedFileChooser.cs
index 915f1e9508..97730e8909 100644
--- a/src/Avalonia.Dialogs/ManagedFileChooser.cs
+++ b/src/Avalonia.Dialogs/ManagedFileChooser.cs
@@ -15,17 +15,17 @@ namespace Avalonia.Dialogs
[TemplatePart("PART_Files", typeof(ListBox))]
public class ManagedFileChooser : TemplatedControl
{
- private Control _quickLinksRoot;
- private ListBox _filesView;
+ private Control? _quickLinksRoot;
+ private ListBox? _filesView;
public ManagedFileChooser()
{
AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
}
- ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel;
+ ManagedFileChooserViewModel? Model => DataContext as ManagedFileChooserViewModel;
- private void OnPointerPressed(object sender, PointerPressedEventArgs e)
+ private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel;
diff --git a/src/Avalonia.Dialogs/ManagedFileChooserOverwritePrompt.cs b/src/Avalonia.Dialogs/ManagedFileChooserOverwritePrompt.cs
new file mode 100644
index 0000000000..117162b2a3
--- /dev/null
+++ b/src/Avalonia.Dialogs/ManagedFileChooserOverwritePrompt.cs
@@ -0,0 +1,31 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Dialogs;
+
+public class ManagedFileChooserOverwritePrompt : TemplatedControl
+{
+ internal event Action? Result;
+
+ private string _fileName = "";
+
+ public static readonly DirectProperty FileNameProperty = AvaloniaProperty.RegisterDirect(
+ "FileName", o => o.FileName, (o, v) => o.FileName = v);
+
+ public string FileName
+ {
+ get => _fileName;
+ set => SetAndRaise(FileNameProperty, ref _fileName, value);
+ }
+
+ public void Confirm()
+ {
+ Result?.Invoke(true);
+ }
+
+ public void Cancel()
+ {
+ Result?.Invoke(false);
+ }
+}
diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
index 86c0bfc588..1157c5516f 100644
--- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
+++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
@@ -1,43 +1,44 @@
-#nullable enable
-
using System;
using System.ComponentModel;
using System.Linq;
+using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
+using Avalonia.Controls.Primitives;
using Avalonia.Platform.Storage;
namespace Avalonia.Dialogs
{
+#if NET6_0_OR_GREATER
+ [SupportedOSPlatform("windows"), SupportedOSPlatform("macos"), SupportedOSPlatform("linux")]
+#endif
public static class ManagedFileDialogExtensions
{
- internal class ManagedStorageProviderFactory : IStorageProviderFactory where T : Window, new()
+ internal class ManagedStorageProviderFactory : IStorageProviderFactory
{
+ private readonly ManagedFileDialogOptions? _options;
+
+ public ManagedStorageProviderFactory(ManagedFileDialogOptions? options)
+ {
+ _options = options;
+ }
+
public IStorageProvider CreateProvider(TopLevel topLevel)
{
- if (topLevel is Window window)
- {
- var options = AvaloniaLocator.Current.GetService();
- return new ManagedStorageProvider(window, options);
- }
- throw new InvalidOperationException("Current platform doesn't support managed picker dialogs");
+ return new ManagedStorageProvider(topLevel, _options);
}
}
-
+
public static AppBuilder UseManagedSystemDialogs(this AppBuilder builder)
{
- builder.AfterSetup(_ =>
- AvaloniaLocator.CurrentMutable.Bind().ToSingleton>());
- return builder;
+ return builder.UseManagedSystemDialogs(null);
}
public static AppBuilder UseManagedSystemDialogs(this AppBuilder builder)
where TWindow : Window, new()
{
- builder.AfterSetup(_ =>
- AvaloniaLocator.CurrentMutable.Bind().ToSingleton>());
- return builder;
+ return builder.UseManagedSystemDialogs(() => new TWindow());
}
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
@@ -48,12 +49,41 @@ namespace Avalonia.Dialogs
public static async Task ShowManagedAsync(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions? options = null) where TWindow : Window, new()
{
- var impl = new ManagedStorageProvider(parent, options);
+ var impl = new ManagedStorageProvider(parent, PrepareOptions(options, () => new TWindow()));
var files = await impl.OpenFilePickerAsync(dialog.ToFilePickerOpenOptions());
return files
.Select(file => file.TryGetLocalPath() ?? file.Name)
.ToArray();
}
+
+ private static ManagedFileDialogOptions? PrepareOptions(
+ ManagedFileDialogOptions? optionsOverride = null,
+ Func? customRootFactory = null)
+ {
+ var options = optionsOverride ?? AvaloniaLocator.Current.GetService();
+ if (options is not null && customRootFactory is not null)
+ {
+ options = options with { ContentRootFactory = customRootFactory };
+ }
+
+ return options;
+ }
+
+ private static AppBuilder UseManagedSystemDialogs(this AppBuilder builder, Func? customFactory)
+ {
+ builder.AfterSetup(_ =>
+ {
+ var options = PrepareOptions(null, customFactory);
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new ManagedStorageProviderFactory(options));
+ if (options?.CustomVolumeInfoProvider is not null)
+ {
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(options.CustomVolumeInfoProvider);
+ }
+ });
+ return builder;
+ }
}
}
diff --git a/src/Avalonia.Dialogs/ManagedFileDialogOptions.cs b/src/Avalonia.Dialogs/ManagedFileDialogOptions.cs
index 56a3eb9122..21cc8c3a4f 100644
--- a/src/Avalonia.Dialogs/ManagedFileDialogOptions.cs
+++ b/src/Avalonia.Dialogs/ManagedFileDialogOptions.cs
@@ -1,7 +1,22 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+
namespace Avalonia.Dialogs
{
- public class ManagedFileDialogOptions
+ public record ManagedFileDialogOptions
{
public bool AllowDirectorySelection { get; set; }
+
+ ///
+ /// Allows to redefine how root volumes are populated in the dialog.
+ ///
+ public IMountedVolumeInfoProvider? CustomVolumeInfoProvider { get; set; }
+
+ ///
+ /// Allows to redefine content root.
+ /// Can be a custom Window or any ContentControl (Popup hosted).
+ ///
+ public Func? ContentRootFactory { get; set; }
}
}
diff --git a/src/Avalonia.Dialogs/ManagedStorageProvider.cs b/src/Avalonia.Dialogs/ManagedStorageProvider.cs
index 3e7c58c689..15b8bf81b4 100644
--- a/src/Avalonia.Dialogs/ManagedStorageProvider.cs
+++ b/src/Avalonia.Dialogs/ManagedStorageProvider.cs
@@ -1,22 +1,24 @@
-#nullable enable
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
using Avalonia.Dialogs.Internal;
+using Avalonia.Layout;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
+using Avalonia.VisualTree;
namespace Avalonia.Dialogs;
-internal class ManagedStorageProvider : BclStorageProvider where T : Window, new()
+internal class ManagedStorageProvider : BclStorageProvider
{
- private readonly Window _parent;
+ private readonly TopLevel? _parent;
private readonly ManagedFileDialogOptions _managedOptions;
- public ManagedStorageProvider(Window parent, ManagedFileDialogOptions? managedOptions)
+ public ManagedStorageProvider(TopLevel? parent, ManagedFileDialogOptions? managedOptions = null)
{
_parent = parent;
_managedOptions = managedOptions ?? new ManagedFileDialogOptions();
@@ -29,7 +31,7 @@ internal class ManagedStorageProvider : BclStorageProvider where T : Window,
public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options)
{
var model = new ManagedFileChooserViewModel(options, _managedOptions);
- var results = await ManagedStorageProvider.Show(model, _parent);
+ var results = await Show(model);
return results.Select(f => new BclStorageFile(new FileInfo(f))).ToArray();
}
@@ -37,7 +39,7 @@ internal class ManagedStorageProvider : BclStorageProvider where T : Window,
public override async Task SaveFilePickerAsync(FilePickerSaveOptions options)
{
var model = new ManagedFileChooserViewModel(options, _managedOptions);
- var results = await ManagedStorageProvider.Show(model, _parent);
+ var results = await Show(model);
return results.FirstOrDefault() is { } result
? new BclStorageFile(new FileInfo(result))
@@ -47,102 +49,176 @@ internal class ManagedStorageProvider : BclStorageProvider where T : Window,
public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
var model = new ManagedFileChooserViewModel(options, _managedOptions);
- var results = await ManagedStorageProvider.Show(model, _parent);
+ var results = await Show(model);
return results.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray();
}
-
- private static async Task Show(ManagedFileChooserViewModel model, Window parent)
+
+ private ContentControl PrepareRoot(ManagedFileChooserViewModel model)
{
- var dialog = new T
+ var root = _managedOptions.ContentRootFactory?.Invoke();
+
+ if (root is null)
{
- Content = new ManagedFileChooser(),
- Title = model.Title,
- DataContext = model
- };
+ if (_parent is not null and not Window)
+ {
+ root = new ContentControl();
+ }
+ else
+ {
+ root = new Window();
+ }
+ }
- dialog.Closed += delegate { model.Cancel(); };
+ root.Content = new ManagedFileChooser();
+ root.DataContext = model;
- string[]? result = null;
+ return root;
+ }
+
+ private Task Show(ManagedFileChooserViewModel model)
+ {
+ var root = PrepareRoot(model);
+
+ if (root is Window window)
+ {
+ return ShowAsWindow(window, model);
+ }
+ else if (_parent is not null)
+ {
+ return ShowAsPopup(root, model);
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ "Managed File Chooser requires existing parent or compatible windowing system.");
+ }
+ }
+
+ private async Task ShowAsWindow(Window window, ManagedFileChooserViewModel model)
+ {
+ var tcs = new TaskCompletionSource();
+ window.Title = model.Title;
+ window.Closed += delegate {
+ model.Cancel();
+ tcs.TrySetResult(true);
+ };
+
+ var result = Array.Empty();
model.CompleteRequested += items =>
{
result = items;
- dialog.Close();
+ window.Close();
};
model.OverwritePrompt += async (filename) =>
{
- var overwritePromptDialog = new Window()
- {
- Title = "Confirm Save As",
- SizeToContent = SizeToContent.WidthAndHeight,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Padding = new Thickness(10),
- MinWidth = 270
- };
-
- string name = Path.GetFileName(filename);
-
- var panel = new DockPanel()
+ if (await ShowOverwritePrompt(filename, window))
{
- HorizontalAlignment = Layout.HorizontalAlignment.Stretch
- };
+ window.Close();
+ }
+ };
- var label = new Label()
- {
- Content = $"{name} already exists.\nDo you want to replace it?"
- };
+ model.CancelRequested += window.Close;
- panel.Children.Add(label);
- DockPanel.SetDock(label, Dock.Top);
+ if (_parent is Window parent)
+ {
+ await window.ShowDialog(parent);
+ }
+ else
+ {
+ window.Show();
+ }
- var buttonPanel = new StackPanel()
- {
- HorizontalAlignment = Layout.HorizontalAlignment.Right,
- Orientation = Layout.Orientation.Horizontal,
- Spacing = 10
- };
+ await tcs.Task;
- var button = new Button()
- {
- Content = "Yes",
- HorizontalAlignment = Layout.HorizontalAlignment.Right
- };
+ return result;
+ }
+
+ private async Task ShowAsPopup(ContentControl root, ManagedFileChooserViewModel model)
+ {
+ var tcs = new TaskCompletionSource();
+ var rootPanel = _parent.FindDescendantOfType()!;
+
+ var popup = new Popup();
+ popup.Placement = PlacementMode.Center;
+ popup.IsLightDismissEnabled = false;
+ popup.Child = root;
+ popup.Width = _parent!.Width;
+ popup.Height = _parent.Height;
+
+ popup.Closed += delegate {
+ model.Cancel();
+ tcs.TrySetResult(true);
+ };
+
+ var result = Array.Empty();
+
+ model.CompleteRequested += items =>
+ {
+ result = items;
+ popup.Close();
+ };
- button.Click += (sender, args) =>
+ model.OverwritePrompt += async (filename) =>
+ {
+ if (await ShowOverwritePrompt(filename, root))
{
- result = new string[1] { filename };
- overwritePromptDialog.Close();
- dialog.Close();
- };
+ popup.Close();
+ }
+ };
- buttonPanel.Children.Add(button);
+ model.CancelRequested += delegate
+ {
+ popup.Close();
+ };
- button = new Button()
- {
- Content = "No",
- HorizontalAlignment = Layout.HorizontalAlignment.Right
- };
+ rootPanel.Children.Add(popup);
+ _parent.SizeChanged += ParentOnSizeChanged;
+ try
+ {
+ popup.Open();
+ await tcs.Task;
+ }
+ finally
+ {
+ rootPanel.Children.Remove(popup);
+ _parent.SizeChanged -= ParentOnSizeChanged;
+ }
- button.Click += (sender, args) =>
+ return result;
+
+ void ParentOnSizeChanged(object? sender, SizeChangedEventArgs e)
+ {
+ if (!popup.IsOpen)
{
- overwritePromptDialog.Close();
- };
-
- buttonPanel.Children.Add(button);
-
- panel.Children.Add(buttonPanel);
- DockPanel.SetDock(buttonPanel, Dock.Bottom);
-
- overwritePromptDialog.Content = panel;
+ _parent.SizeChanged -= ParentOnSizeChanged;
+ }
+
+ popup.Width = _parent!.Width;
+ popup.Height = _parent.Height;
+ }
+ }
- await overwritePromptDialog.ShowDialog(dialog);
+ private static async Task ShowOverwritePrompt(string filename, ContentControl root)
+ {
+ var tcs = new TaskCompletionSource();
+ var prompt = new ManagedFileChooserOverwritePrompt
+ {
+ FileName = Path.GetFileName(filename)
};
+ prompt.Result += (r) => tcs.TrySetResult(r);
+
+ var flyout = new Flyout();
+ flyout.Closed += (_, _) => tcs.TrySetResult(false);
+ flyout.Content = prompt;
+ flyout.Placement = PlacementMode.Center;
+ flyout.ShowAt(root);
- model.CancelRequested += dialog.Close;
+ var promptResult = await tcs.Task;
+ flyout.Hide();
- await dialog.ShowDialog(parent);
- return result ?? Array.Empty();
+ return promptResult;
}
}
diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
index 28a288f22a..27a1af95ff 100644
--- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
+++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
index a0bfad6fe4..94709926e7 100644
--- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
+++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using Avalonia.Reactive;
using Tmds.DBus.SourceGenerator;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
@@ -37,21 +38,21 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
return await (_modern?.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time) ?? Task.FromResult(false));
}
- public ValueTask WatchCommitStringAsync(Action handler) =>
+ public ValueTask WatchCommitStringAsync(Action handler) =>
_old?.WatchCommitStringAsync(handler)
?? _modern?.WatchCommitStringAsync(handler)
- ?? new ValueTask(default(IDisposable?));
+ ?? new ValueTask(Disposable.Empty);
- public ValueTask WatchForwardKeyAsync(Action handler) =>
+ public ValueTask WatchForwardKeyAsync(Action handler) =>
_old?.WatchForwardKeyAsync(handler)
?? _modern?.WatchForwardKeyAsync((e, ev) => handler.Invoke(e, (ev.keyval, ev.state, ev.type ? 1 : 0)))
- ?? new ValueTask(default(IDisposable?));
+ ?? new ValueTask(Disposable.Empty);
- public ValueTask WatchUpdateFormattedPreeditAsync(
- Action handler) =>
- _old?.WatchUpdateFormattedPreeditAsync(handler)
- ?? _modern?.WatchUpdateFormattedPreeditAsync(handler)
- ?? new(default);
+ public ValueTask WatchUpdateFormattedPreeditAsync(
+ Action handler) =>
+ _old?.WatchUpdateFormattedPreeditAsync(handler!)
+ ?? _modern?.WatchUpdateFormattedPreeditAsync(handler!)
+ ?? new ValueTask(Disposable.Empty);
public Task SetCapacityAsync(uint flags) =>
_old?.SetCapacityAsync(flags) ?? _modern?.SetCapabilityAsync(flags) ?? Task.CompletedTask;
diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
index 12e365e28f..efd173d9a6 100644
--- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
+++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
@@ -46,11 +46,11 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
return true;
}
- private void OnPreedit(Exception? arg1, ((string, int)[] str, int cursorpos) args)
+ private void OnPreedit(Exception? arg1, ((string?, int)[]? str, int cursorpos) args)
{
int? cursor = null;
- string preeditString = null;
- if (args.str != null! && args.str.Length > 0)
+ string? preeditString = null;
+ if (args.str != null && args.str.Length > 0)
{
preeditString = string.Join("", args.str.Select(x => x.Item1));
diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs
index b9a11b865c..ef9b7c26c3 100644
--- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs
+++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs
@@ -38,8 +38,9 @@ namespace Avalonia.FreeDesktop
private bool _resetQueued;
private int _nextId = 1;
- public DBusMenuExporterImpl(Connection connection, IntPtr xid) : this()
+ public DBusMenuExporterImpl(Connection connection, IntPtr xid)
{
+ InitBackingProperties();
Connection = connection;
_xid = (uint)xid.ToInt32();
Path = GenerateDBusMenuObjPath;
@@ -47,8 +48,9 @@ namespace Avalonia.FreeDesktop
_ = InitializeAsync();
}
- public DBusMenuExporterImpl(Connection connection, string path) : this()
+ public DBusMenuExporterImpl(Connection connection, string path)
{
+ InitBackingProperties();
Connection = connection;
_appMenu = false;
Path = path;
@@ -56,7 +58,7 @@ namespace Avalonia.FreeDesktop
_ = InitializeAsync();
}
- private DBusMenuExporterImpl()
+ private void InitBackingProperties()
{
BackingProperties.Version = 4;
BackingProperties.Status = string.Empty;
diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
index c7552823d4..bfb130aade 100644
--- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
+++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
@@ -40,8 +40,15 @@ namespace Avalonia.FreeDesktop
{
try
{
- var value = await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme");
- return ToColorScheme(((value.Value as DBusVariantItem)!.Value as DBusUInt32Item)!.Value);
+ var version = await _settings!.GetVersionPropertyAsync();
+ DBusVariantItem value;
+ if (version >= 2)
+ value = await _settings!.ReadOneAsync("org.freedesktop.appearance", "color-scheme");
+ else
+ value = (DBusVariantItem)(await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme")).Value;
+ if (value.Value is DBusUInt32Item dBusUInt32Item)
+ return ToColorScheme(dBusUInt32Item.Value);
+ return null;
}
catch (DBusException)
{
@@ -53,8 +60,15 @@ namespace Avalonia.FreeDesktop
{
try
{
- var value = await _settings!.ReadAsync("org.kde.kdeglobals.General", "AccentColor");
- return ToAccentColor(((value.Value as DBusVariantItem)!.Value as DBusStringItem)!.Value);
+ var version = await _settings!.GetVersionPropertyAsync();
+ DBusVariantItem value;
+ if (version >= 2)
+ value = await _settings!.ReadOneAsync("org.freedesktop.appearance", "accent-color");
+ else
+ value = (DBusVariantItem)(await _settings!.ReadAsync("org.freedesktop.appearance", "accent-color")).Value;
+ if (value.Value is DBusStructItem dBusStructItem)
+ return ToAccentColor(dBusStructItem);
+ return null;
}
catch (DBusException)
{
@@ -62,7 +76,7 @@ namespace Avalonia.FreeDesktop
}
}
- private async void SettingsChangedHandler(Exception? exception, (string @namespace, string key, DBusVariantItem value) valueTuple)
+ private void SettingsChangedHandler(Exception? exception, (string @namespace, string key, DBusVariantItem value) valueTuple)
{
if (exception is not null)
return;
@@ -71,7 +85,11 @@ namespace Avalonia.FreeDesktop
{
case ("org.freedesktop.appearance", "color-scheme", { } colorScheme):
_themeVariant = ToColorScheme((colorScheme.Value as DBusUInt32Item)!.Value);
- _accentColor = await TryGetAccentColorAsync();
+ _lastColorValues = BuildPlatformColorValues();
+ OnColorValuesChanged(_lastColorValues!);
+ break;
+ case ("org.freedesktop.appearance", "accent-color", { } accentColor):
+ _accentColor = ToAccentColor((accentColor.Value as DBusStructItem)!);
_lastColorValues = BuildPlatformColorValues();
OnColorValuesChanged(_lastColorValues!);
break;
@@ -92,18 +110,27 @@ namespace Avalonia.FreeDesktop
private static PlatformThemeVariant ToColorScheme(uint value)
{
/*
- 0: No preference
- 1: Prefer dark appearance
- 2: Prefer light appearance
+ 0: No preference
+ 1: Prefer dark appearance
+ 2: Prefer light appearance
*/
var isDark = value == 1;
return isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light;
}
- private static Color ToAccentColor(string value)
+ private static Color? ToAccentColor(DBusStructItem value)
{
- var rgb = value.Split(',');
- return new Color(255, byte.Parse(rgb[0]), byte.Parse(rgb[1]), byte.Parse(rgb[2]));
+ /*
+ Indicates the system's preferred accent color as a tuple of RGB values
+ in the sRGB color space, in the range [0,1].
+ Out-of-range RGB values should be treated as an unset accent color.
+ */
+ var r = (value[0] as DBusDoubleItem)!.Value;
+ var g = (value[1] as DBusDoubleItem)!.Value;
+ var b = (value[2] as DBusDoubleItem)!.Value;
+ if (r is < 0 or > 1 || g is < 0 or > 1 || b is < 0 or > 1)
+ return null;
+ return Color.FromRgb((byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
}
}
}
diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
index 096093347b..1b22393337 100644
--- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
+++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
@@ -20,27 +20,30 @@ namespace Avalonia.FreeDesktop
return null;
var dbusFileChooser = new OrgFreedesktopPortalFileChooser(DBusHelper.Connection, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop");
+ uint version = 0;
try
{
- await dbusFileChooser.GetVersionPropertyAsync();
+ version = await dbusFileChooser.GetVersionPropertyAsync();
}
catch
{
return null;
}
- return new DBusSystemDialog(DBusHelper.Connection, handle, dbusFileChooser);
+ return new DBusSystemDialog(DBusHelper.Connection, handle, dbusFileChooser, version);
}
private readonly Connection _connection;
private readonly OrgFreedesktopPortalFileChooser _fileChooser;
private readonly IPlatformHandle _handle;
+ private readonly uint _version;
- private DBusSystemDialog(Connection connection, IPlatformHandle handle, OrgFreedesktopPortalFileChooser fileChooser)
+ private DBusSystemDialog(Connection connection, IPlatformHandle handle, OrgFreedesktopPortalFileChooser fileChooser, uint version)
{
_connection = connection;
_fileChooser = fileChooser;
_handle = handle;
+ _version = version;
}
public override bool CanOpen => true;
@@ -58,6 +61,8 @@ namespace Avalonia.FreeDesktop
if (filters is not null)
chooserOptions.Add("filters", filters);
+ if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath && _version >= 4)
+ chooserOptions.Add("current_folder", new DBusVariantItem("ay", new DBusByteArrayItem(Encoding.UTF8.GetBytes(folderPath + "\0"))));
chooserOptions.Add("multiple", new DBusVariantItem("b", new DBusBoolItem(options.AllowMultiple)));
objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
@@ -88,7 +93,7 @@ namespace Avalonia.FreeDesktop
if (options.SuggestedFileName is { } currentName)
chooserOptions.Add("current_name", new DBusVariantItem("s", new DBusStringItem(currentName)));
if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath)
- chooserOptions.Add("current_folder", new DBusVariantItem("ay", new DBusByteArrayItem(Encoding.UTF8.GetBytes(folderPath))));
+ chooserOptions.Add("current_folder", new DBusVariantItem("ay", new DBusByteArrayItem(Encoding.UTF8.GetBytes(folderPath + "\0"))));
objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath);
@@ -120,7 +125,9 @@ namespace Avalonia.FreeDesktop
{ "directory", new DBusVariantItem("b", new DBusBoolItem(true)) },
{ "multiple", new DBusVariantItem("b", new DBusBoolItem(options.AllowMultiple)) }
};
-
+ if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath && _version >= 4)
+ chooserOptions.Add("current_folder", new DBusVariantItem("ay", new DBusByteArrayItem(Encoding.UTF8.GetBytes(folderPath + "\0"))));
+
var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath);
var tsc = new TaskCompletionSource();
diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml
index 669997a3df..1ae6752560 100644
--- a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml
+++ b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml
@@ -43,13 +43,21 @@
Unknown values should be treated as 0 (no preference).
+
+ org.freedesktop.appearance accent-color (ddd)
+
+ Indicates the system's preferred accent color as a tuple of RGB values
+ in the sRGB color space, in the range [0,1].
+ Out-of-range RGB values should be treated as an unset accent color.
+
+
Implementations can provide other keys; they are entirely
implementation details that are undocumented. If you are a
toolkit and want to use this please open an issue.
- This documentation describes version 1 of this interface.
+ This documentation describes version 2 of this interface.
-->
@@ -73,8 +81,31 @@
@value: The value @key is set to.
Reads a single value. Returns an error on any unknown namespace or key.
+
+ Deprecated, use ReadOne instead. The value argument was intended to have
+ the value inside one layer of variant as it is in ReadOne, for example
+ `<string "hello">` in GVariant text notation; but it is actually
+ returned inside two layers of variant, for example
+ `<<string "hello">>`.
-->
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Native/DispatcherImpl.cs b/src/Avalonia.Native/DispatcherImpl.cs
index fd8ef567f4..16ec0f7ba6 100644
--- a/src/Avalonia.Native/DispatcherImpl.cs
+++ b/src/Avalonia.Native/DispatcherImpl.cs
@@ -6,7 +6,6 @@ using System.Runtime.ExceptionServices;
using System.Threading;
using Avalonia.Native.Interop;
using Avalonia.Threading;
-using MicroCom.Runtime;
namespace Avalonia.Native;
@@ -24,9 +23,9 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherImplWithEx
_native.SetEvents(events);
}
- public event Action Signaled;
- public event Action Timer;
- public event Action ReadyForBackgroundProcessing;
+ public event Action? Signaled;
+ public event Action? Timer;
+ public event Action? ReadyForBackgroundProcessing;
private class Events : NativeCallbackBase, IAvnPlatformThreadingInterfaceEvents
{
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index f1b7f6876c..322469b142 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -1,21 +1,17 @@
using System;
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Native.Interop;
-using Avalonia.OpenGL;
using Avalonia.Platform;
-using Avalonia.Platform.Interop;
namespace Avalonia.Native
{
internal class WindowImpl : WindowBaseImpl, IWindowImpl
{
private readonly AvaloniaNativePlatformOptions _opts;
- private readonly AvaloniaNativeGlPlatformGraphics _graphics;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
private DoubleClickHelper _doubleClickHelper;
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index a2874a38d0..93d63160ea 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -143,7 +143,7 @@ namespace Avalonia.Native
class FramebufferRenderTarget : IFramebufferRenderTarget
{
private readonly WindowBaseImpl _parent;
- private IAvnSoftwareRenderTarget? _target;
+ private IAvnSoftwareRenderTarget _target;
public FramebufferRenderTarget(WindowBaseImpl parent, IAvnSoftwareRenderTarget target)
{
diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
index 18bf01ece7..cc472fd72f 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
@@ -127,7 +127,8 @@
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
+ VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
+ IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}">
diff --git a/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml
deleted file mode 100644
index f277c6f9ae..0000000000
--- a/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
index 34aecd03e0..6ae561e821 100644
--- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
@@ -58,7 +58,6 @@
-
@@ -77,6 +76,4 @@
-
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
index 3757f685bf..886846a589 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
@@ -37,6 +37,7 @@
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}"
+ IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- OK
- Cancel
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OK
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml
index 531f11a90b..63352d6ee3 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml
@@ -146,6 +146,7 @@
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
+ DeferThumbDrag="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
ViewportSize="{TemplateBinding ViewportSize}"
Orientation="{TemplateBinding Orientation}"
IsDirectionReversed="True">
@@ -223,6 +224,7 @@
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
+ DeferThumbDrag="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
ViewportSize="{TemplateBinding ViewportSize}"
Orientation="{TemplateBinding Orientation}">
diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
index 886a903a43..611c7dc49b 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
@@ -27,8 +27,6 @@
+ Grid.Row="1"/>
+ Grid.Column="1"/>
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
index e7704ee4e5..3d2d33919c 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
@@ -131,7 +131,6 @@
+
+
+
@@ -228,6 +232,10 @@
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
index 0fdcaf39f8..24854694a8 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
@@ -31,6 +31,7 @@
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
+ IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
index a54857abc9..910d8fba68 100644
--- a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
@@ -62,7 +62,8 @@
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
+ VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
+ IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}">
diff --git a/src/Avalonia.Themes.Simple/Controls/ContentControl.xaml b/src/Avalonia.Themes.Simple/Controls/ContentControl.xaml
deleted file mode 100644
index 571a276a49..0000000000
--- a/src/Avalonia.Themes.Simple/Controls/ContentControl.xaml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Simple/Controls/ListBox.xaml b/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
index 73c27db756..f0c6a88e7a 100644
--- a/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
@@ -22,6 +22,7 @@
Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
+ IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
VerticalSnapPointsType="{TemplateBinding (ScrollViewer.VerticalSnapPointsType)}"
HorizontalSnapPointsType="{TemplateBinding (ScrollViewer.HorizontalSnapPointsType)}">
diff --git a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
index 24e5247139..33864d2d51 100644
--- a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
@@ -48,188 +48,230 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Show hidden files
-
-
-
-
-
-
- OK
- Cancel
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Show hidden files
+
+
+
+
+
+
+ OK
+ Cancel
+
+
+
+
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml
index 8b647c8299..e8dfd3736f 100644
--- a/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ScrollBar.xaml
@@ -27,6 +27,7 @@
Minimum="{TemplateBinding Minimum}"
Orientation="{TemplateBinding Orientation}"
ViewportSize="{TemplateBinding ViewportSize}"
+ DeferThumbDrag="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
Value="{TemplateBinding Value,
Mode=TwoWay}">
@@ -77,6 +78,7 @@
Minimum="{TemplateBinding Minimum}"
Orientation="{TemplateBinding Orientation}"
ViewportSize="{TemplateBinding ViewportSize}"
+ DeferThumbDrag="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
Value="{TemplateBinding Value,
Mode=TwoWay}">
diff --git a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
index 2b36b118c3..ea62f757c5 100644
--- a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
@@ -31,7 +31,7 @@
Orientation="Vertical"/>
+ Background="{DynamicResource ThemeControlMidBrush}"/>
diff --git a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
index 51a21720b9..62a8fd1955 100644
--- a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
@@ -26,7 +26,6 @@
-
@@ -75,6 +74,4 @@
-
-
diff --git a/src/Avalonia.Themes.Simple/Controls/TextBox.xaml b/src/Avalonia.Themes.Simple/Controls/TextBox.xaml
index 3f06a33dc1..16eaba5489 100644
--- a/src/Avalonia.Themes.Simple/Controls/TextBox.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TextBox.xaml
@@ -157,6 +157,11 @@
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" />
+
+
+
+
diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs
index 035354f337..4801befbed 100644
--- a/src/Avalonia.X11/Glx/GlxContext.cs
+++ b/src/Avalonia.X11/Glx/GlxContext.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Threading;
@@ -9,17 +11,17 @@ namespace Avalonia.X11.Glx
{
internal class GlxContext : IGlContext
{
- public IntPtr Handle { get; }
+ public IntPtr Handle { get; }
public GlxInterface Glx { get; }
- private readonly GlxContext _sharedWith;
+ private readonly GlxContext? _sharedWith;
private readonly X11Info _x11;
private readonly IntPtr _defaultXid;
private readonly bool _ownsPBuffer;
private readonly object _lock = new object();
- private ExternalObjectsOpenGlExtensionFeature? _externalObjects;
+ private readonly ExternalObjectsOpenGlExtensionFeature? _externalObjects;
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display,
- GlxContext sharedWith,
+ GlxContext? sharedWith,
GlVersion version, int sampleCount, int stencilSize,
X11Info x11, IntPtr defaultXid,
bool ownsPBuffer)
@@ -49,13 +51,13 @@ namespace Avalonia.X11.Glx
private class RestoreContext : IDisposable
{
- private GlxInterface _glx;
- private IntPtr _defaultDisplay;
+ private readonly GlxInterface _glx;
+ private readonly IntPtr _defaultDisplay;
private readonly object _l;
- private IntPtr _display;
- private IntPtr _context;
- private IntPtr _read;
- private IntPtr _draw;
+ private readonly IntPtr _display;
+ private readonly IntPtr _context;
+ private readonly IntPtr _read;
+ private readonly IntPtr _draw;
public RestoreContext(GlxInterface glx, IntPtr defaultDisplay, object l)
{
@@ -97,7 +99,7 @@ namespace Avalonia.X11.Glx
public bool CanCreateSharedContext => true;
- public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) =>
+ public IGlContext CreateSharedContext(IEnumerable? preferredVersions = null) =>
Display.CreateContext(_sharedWith ?? this);
public IDisposable MakeCurrent(IntPtr xid)
@@ -129,7 +131,7 @@ namespace Avalonia.X11.Glx
Glx.DestroyPbuffer(_x11.DeferredDisplay, _defaultXid);
}
- public object TryGetFeature(Type featureType)
+ public object? TryGetFeature(Type featureType)
{
if (featureType == typeof(IGlContextExternalObjectsFeature))
return _externalObjects;
diff --git a/src/Avalonia.X11/Screens/X11Screen.Providers.cs b/src/Avalonia.X11/Screens/X11Screen.Providers.cs
index 64cfd7fa79..73930a91af 100644
--- a/src/Avalonia.X11/Screens/X11Screen.Providers.cs
+++ b/src/Avalonia.X11/Screens/X11Screen.Providers.cs
@@ -35,20 +35,20 @@ internal partial class X11Screens
internal interface IX11RawScreenInfoProvider
{
X11Screen[] Screens { get; }
- event Action Changed;
+ event Action? Changed;
}
private class Randr15ScreensImpl : IX11RawScreenInfoProvider
{
- private X11Screen[] _cache;
+ private X11Screen[]? _cache;
private readonly X11Info _x11;
private readonly IntPtr _window;
// Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4
private const int EDIDStructureLength = 32;
- public event Action Changed;
+ public event Action? Changed;
public Randr15ScreensImpl(AvaloniaX11Platform platform)
{
@@ -129,7 +129,7 @@ internal partial class X11Screens
}
}
- screens[c] = new X11Screen(bounds, mon.Primary != 0, name, pSize);
+ screens[c] = new X11Screen(bounds, mon.Primary != 0, name ?? string.Empty, pSize);
}
XFree(new IntPtr(monitors));
@@ -142,8 +142,13 @@ internal partial class X11Screens
private class FallbackScreensImpl : IX11RawScreenInfoProvider
{
private readonly X11Info _info;
- public event Action? Changed;
-
+
+ public event Action? Changed
+ {
+ add { }
+ remove { }
+ }
+
public FallbackScreensImpl(AvaloniaX11Platform platform)
{
_info = platform.Info;
@@ -169,4 +174,4 @@ internal partial class X11Screens
public X11Screen[] Screens { get; private set; } = new[]
{ new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null) };
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.X11/Screens/X11Screens.Scaling.cs b/src/Avalonia.X11/Screens/X11Screens.Scaling.cs
index c57789fcbe..bd398cfc1d 100644
--- a/src/Avalonia.X11/Screens/X11Screens.Scaling.cs
+++ b/src/Avalonia.X11/Screens/X11Screens.Scaling.cs
@@ -174,7 +174,7 @@ internal partial class X11Screens
try
{
var split = screenFactorsString.Split(';').Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
- if (split[0].Contains("="))
+ if (split[0].Contains('='))
{
userConfig = new UserScalingConfiguration
{
@@ -197,7 +197,7 @@ internal partial class X11Screens
}
- if (globalFactorString == null && screenFactorsString == null && usePhysicalDpi == null)
+ if (globalFactorString == null && screenFactorsString == null)
return null;
return (userConfig, globalFactor ?? 1, usePhysicalDpi);
@@ -246,4 +246,4 @@ internal partial class X11Screens
return provider;
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.X11/X11Window.Xim.cs b/src/Avalonia.X11/X11Window.Xim.cs
index 3dc860d612..e016908484 100644
--- a/src/Avalonia.X11/X11Window.Xim.cs
+++ b/src/Avalonia.X11/X11Window.Xim.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
using System.Threading.Tasks;
using Avalonia.FreeDesktop;
@@ -6,6 +8,7 @@ using Avalonia.Input.TextInput;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using static Avalonia.X11.XLib;
+
namespace Avalonia.X11
{
internal partial class X11Window
@@ -62,7 +65,7 @@ namespace Avalonia.X11
UpdateActive();
}
- public void SetClient(TextInputMethodClient client)
+ public void SetClient(TextInputMethodClient? client)
{
_client = client;
UpdateActive();
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 06c1d7b2e1..f4e3be29c4 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -577,7 +577,7 @@ namespace Avalonia.X11
else if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_TAKE_FOCUS && _platform.Options.EnableInputFocusProxy)
{
IntPtr time = ev.ClientMessageEvent.ptr2;
- XSetInputFocus(_x11.Display, _focusProxy._handle, RevertTo.Parent, time);
+ XSetInputFocus(_x11.Display, _focusProxy!._handle, RevertTo.Parent, time);
}
}
}
@@ -787,8 +787,8 @@ namespace Avalonia.X11
return;
Input?.Invoke(args);
- if (!args.Handled && args is RawKeyEventArgsWithText text && !string.IsNullOrEmpty(text.Text))
- Input?.Invoke(new RawTextInputEventArgs(_keyboard, args.Timestamp, _inputRoot, text.Text));
+ if (!args.Handled && args is RawKeyEventArgsWithText { Text: { Length: > 0 } text })
+ Input?.Invoke(new RawTextInputEventArgs(_keyboard, args.Timestamp, _inputRoot, text));
}
public void ScheduleXI2Input(RawInputEventArgs args)
@@ -977,7 +977,7 @@ namespace Avalonia.X11
return false;
}
- public void SetParent(IWindowImpl parent)
+ public void SetParent(IWindowImpl? parent)
{
if (parent == null || parent.Handle == null || parent.Handle.Handle == IntPtr.Zero)
XDeleteProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_TRANSIENT_FOR);
@@ -1101,6 +1101,7 @@ namespace Avalonia.X11
{
_position = value;
PositionChanged?.Invoke(value);
+ UpdateScaling();
}
}
}
@@ -1121,7 +1122,9 @@ namespace Avalonia.X11
else
{
XRaiseWindow(_x11.Display, _handle);
- XSetInputFocus(_x11.Display, _focusProxy._handle, 0, IntPtr.Zero);
+
+ if (_focusProxy is not null)
+ XSetInputFocus(_x11.Display, _focusProxy._handle, 0, IntPtr.Zero);
}
}
diff --git a/src/Avalonia.X11/XResources.cs b/src/Avalonia.X11/XResources.cs
index a4e832cb60..52df657ce9 100644
--- a/src/Avalonia.X11/XResources.cs
+++ b/src/Avalonia.X11/XResources.cs
@@ -49,11 +49,11 @@ internal class XResources
return value;
}
- string ReadResourcesString()
+ string? ReadResourcesString()
{
XGetWindowProperty(_x11.Display, _x11.RootWindow, _x11.Atoms.XA_RESOURCE_MANAGER,
IntPtr.Zero, new IntPtr(0x7fffffff),
- false, _x11.Atoms.XA_STRING, out var actualType, out var actualFormat,
+ false, _x11.Atoms.XA_STRING, out _, out var actualFormat,
out var nitems, out _, out var prop);
try
{
@@ -72,4 +72,4 @@ internal class XResources
if (atom == _x11.Atoms.XA_RESOURCE_MANAGER)
UpdateResources();
}
-}
\ No newline at end of file
+}
diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
index c2c2754de3..19d18e2e29 100644
--- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
+++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
@@ -10,15 +10,15 @@
-
-
+
+
-
+
@@ -32,7 +32,7 @@
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest"
ContentRoot="$(MSBuildThisFileDirectory)../Avalonia.Browser/wwwroot/"
- BasePath="_content\$(PackageId)"
+ BasePath="_content/$(PackageId)"
CandidateAssets="@(_AvaloniaWebAssets)"
RelativePathFilter="**.js">
diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj
index 4424dada94..ec65717cbb 100644
--- a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj
+++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj
@@ -10,25 +10,25 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
true
- build\
+ build/
true
- build\;buildTransitive\
+ build/;buildTransitive/
true
@@ -36,7 +36,7 @@
true
- build\wwwroot;buildTransitive\wwwroot
+ build/wwwroot;buildTransitive/wwwroot
diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.props b/src/Browser/Avalonia.Browser/Avalonia.Browser.props
index 668dd20789..1cff1c8be5 100644
--- a/src/Browser/Avalonia.Browser/Avalonia.Browser.props
+++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.props
@@ -1,5 +1,7 @@
- 16384000
+ True
+ True
+ True
diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.targets b/src/Browser/Avalonia.Browser/Avalonia.Browser.targets
index 22363b33d8..8740564ea9 100644
--- a/src/Browser/Avalonia.Browser/Avalonia.Browser.targets
+++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.targets
@@ -1,37 +1,33 @@
-
-
-
-
-
-
- True
- $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"
+ $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)/interop.js"
$(EmccExtraLDFlags) -sERROR_ON_UNDEFINED_SYMBOLS=0
- true
-
- true
- full
- true
- -Oz
- -Oz
- false
- false
- 0
- false
- true
- false
- false
- false
- false
- false
- false
- true
- true
- en
- false
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs
index dde4746394..86323afbbd 100644
--- a/src/Browser/Avalonia.Browser/AvaloniaView.cs
+++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs
@@ -12,6 +12,7 @@ using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
+using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
@@ -138,11 +139,8 @@ namespace Avalonia.Browser
}
else
{
- //var rasterInitialized = _interop.InitRaster();
- //Console.WriteLine("raster initialized: {0}", rasterInitialized);
-
- //_topLevelImpl.SetSurface(ColorType,
- // new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
+ Logger.TryGet(LogEventLevel.Error, LogArea.BrowserPlatform)?
+ .Log(this, "[Avalonia]: Unable to initialize Canvas surface.");
}
CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
@@ -442,6 +440,7 @@ namespace Avalonia.Browser
return;
}
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender);
ManualTriggerRenderTimer.Instance.RaiseTick();
}
diff --git a/src/Browser/Avalonia.Browser/BrowserDispatcherImpl.cs b/src/Browser/Avalonia.Browser/BrowserDispatcherImpl.cs
new file mode 100644
index 0000000000..6ee6c719a7
--- /dev/null
+++ b/src/Browser/Avalonia.Browser/BrowserDispatcherImpl.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+using Avalonia.Browser.Interop;
+using Avalonia.Threading;
+
+namespace Avalonia.Browser;
+
+internal class BrowserDispatcherImpl : IDispatcherImpl
+{
+ private readonly Thread _thread;
+ private readonly Stopwatch _clock;
+ private bool _signaled;
+ private int? _timerId;
+
+ private readonly Action _timerCallback;
+ private readonly Action _signalCallback;
+
+ public BrowserDispatcherImpl()
+ {
+ _thread = Thread.CurrentThread;
+ _clock = Stopwatch.StartNew();
+
+ _timerCallback = () => Timer?.Invoke();
+ _signalCallback = () =>
+ {
+ _signaled = false;
+ Signaled?.Invoke();
+ };
+ }
+
+ public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _thread;
+
+ public long Now => _clock.ElapsedMilliseconds;
+
+ public event Action? Signaled;
+ public event Action? Timer;
+
+ public void Signal()
+ {
+ if (_signaled)
+ return;
+
+ // NOTE: by HTML5 spec minimal timeout is 4ms, but Chrome seems to work well with 1ms as well.
+ var interval = 1;
+ CanvasHelper.SetTimeout(_signalCallback, interval);
+ }
+
+ public void UpdateTimer(long? dueTimeInMs)
+ {
+ if (_timerId is { } timerId)
+ {
+ _timerId = null;
+ CanvasHelper.ClearInterval(timerId);
+ }
+
+ if (dueTimeInMs.HasValue)
+ {
+ var interval = Math.Max(1, dueTimeInMs.Value - _clock.ElapsedMilliseconds);
+ _timerId = CanvasHelper.SetInterval(_timerCallback, (int)interval);
+ }
+ }
+}
diff --git a/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs
index 27a2b1dcb7..9e182eaa09 100644
--- a/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs
+++ b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs
@@ -39,4 +39,16 @@ internal static partial class CanvasHelper
JSObject canvas,
string canvasId,
[JSMarshalAs] Action renderFrameCallback);
+
+ [JSImport("globalThis.setTimeout")]
+ public static partial int SetTimeout([JSMarshalAs] Action callback, int intervalMs);
+
+ [JSImport("globalThis.clearTimeout")]
+ public static partial int ClearTimeout(int id);
+
+ [JSImport("globalThis.setInterval")]
+ public static partial int SetInterval([JSMarshalAs] Action callback, int intervalMs);
+
+ [JSImport("globalThis.clearInterval")]
+ public static partial int ClearInterval(int id);
}
diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
index a23cd01910..2db0e2aec3 100644
--- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs
+++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
@@ -1,5 +1,4 @@
using System;
-using System.Threading;
using Avalonia.Browser.Interop;
using Avalonia.Browser.Skia;
using Avalonia.Input;
@@ -8,88 +7,49 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
-namespace Avalonia.Browser
-{
- internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformThreadingInterface
- {
- private bool _signaled;
- private static KeyboardDevice? s_keyboard;
-
- public IWindowImpl CreateWindow() => throw new NotSupportedException("Browser doesn't support windowing platform. In order to display a single-view content, set ISingleViewApplicationLifetime.MainView.");
-
- IWindowImpl IWindowingPlatform.CreateEmbeddableWindow()
- {
- throw new NotImplementedException("Browser doesn't support embeddable windowing platform.");
- }
-
- public ITrayIconImpl? CreateTrayIcon()
- {
- return null;
- }
-
- public static KeyboardDevice Keyboard => s_keyboard ??
- throw new InvalidOperationException("BrowserWindowingPlatform not registered.");
-
- public static void Register()
- {
- var instance = new BrowserWindowingPlatform();
-
- s_keyboard = new KeyboardDevice();
- AvaloniaLocator.CurrentMutable
- .Bind().ToSingleton()
- .Bind().ToSingleton()
- .Bind().ToConstant(s_keyboard)
- .Bind().ToSingleton()
- .Bind().ToConstant(instance)
- .Bind().ToConstant(ManualTriggerRenderTimer.Instance)
- .Bind().ToConstant(instance)
- .Bind().ToConstant(new BrowserSkiaGraphics())
- .Bind().ToSingleton()
- .Bind().ToSingleton();
+namespace Avalonia.Browser;
- if (AvaloniaLocator.Current.GetService() is { } options
- && options.RegisterAvaloniaServiceWorker)
- {
- var swPath = AvaloniaModule.ResolveServiceWorkerPath();
- AvaloniaModule.RegisterServiceWorker(swPath, options.AvaloniaServiceWorkerScope);
- }
- }
-
- public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
- {
- return new Timer(_ =>
- {
- Dispatcher.UIThread.RunJobs(priority);
- tick();
- }, null, interval, interval);
- }
+internal class BrowserWindowingPlatform : IWindowingPlatform
+{
+ private static KeyboardDevice? s_keyboard;
- public void Signal(DispatcherPriority priority)
- {
- if (_signaled)
- return;
+ public IWindowImpl CreateWindow() => throw new NotSupportedException("Browser doesn't support windowing platform. In order to display a single-view content, set ISingleViewApplicationLifetime.MainView.");
- _signaled = true;
- var interval = TimeSpan.FromMilliseconds(1);
+ IWindowImpl IWindowingPlatform.CreateEmbeddableWindow()
+ {
+ throw new NotImplementedException("Browser doesn't support embeddable windowing platform.");
+ }
- IDisposable? disp = null;
- disp = new Timer(_ =>
- {
- _signaled = false;
- disp?.Dispose();
+ public ITrayIconImpl? CreateTrayIcon()
+ {
+ return null;
+ }
- Signaled?.Invoke(null);
- }, null, interval, interval);
- }
+ public static KeyboardDevice Keyboard => s_keyboard ??
+ throw new InvalidOperationException("BrowserWindowingPlatform not registered.");
- public bool CurrentThreadIsLoopThread
+ public static void Register()
+ {
+ var instance = new BrowserWindowingPlatform();
+
+ s_keyboard = new KeyboardDevice();
+ AvaloniaLocator.CurrentMutable
+ .Bind().ToSingleton()
+ .Bind().ToSingleton()
+ .Bind().ToConstant(s_keyboard)
+ .Bind().ToSingleton()
+ .Bind().ToSingleton()
+ .Bind().ToConstant(ManualTriggerRenderTimer.Instance)
+ .Bind().ToConstant(instance)
+ .Bind().ToConstant(new BrowserSkiaGraphics())
+ .Bind().ToSingleton()
+ .Bind().ToSingleton();
+
+ if (AvaloniaLocator.Current.GetService() is { } options
+ && options.RegisterAvaloniaServiceWorker)
{
- get
- {
- return true; // Browser is single threaded.
- }
+ var swPath = AvaloniaModule.ResolveServiceWorkerPath();
+ AvaloniaModule.RegisterServiceWorker(swPath, options.AvaloniaServiceWorkerScope);
}
-
- public event Action? Signaled;
}
}
diff --git a/src/Headless/Avalonia.Headless/AvaloniaTestApplicationAttribute.cs b/src/Headless/Avalonia.Headless/AvaloniaTestApplicationAttribute.cs
index 9159657ec4..c303ea33fa 100644
--- a/src/Headless/Avalonia.Headless/AvaloniaTestApplicationAttribute.cs
+++ b/src/Headless/Avalonia.Headless/AvaloniaTestApplicationAttribute.cs
@@ -9,6 +9,7 @@ namespace Avalonia.Headless;
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class AvaloniaTestApplicationAttribute : Attribute
{
+ [DynamicallyAccessedMembers(HeadlessUnitTestSession.DynamicallyAccessed)]
public Type AppBuilderEntryPointType { get; }
///
diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
index 0ccff00c23..5c2e320383 100644
--- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -369,7 +369,7 @@ namespace Avalonia.Headless
}
- public void SetParent(IWindowImpl parent)
+ public void SetParent(IWindowImpl? parent)
{
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
index 300cbc2689..fba9862a85 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
@@ -19,7 +19,10 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
public LibInputBackend()
{
var ctx = libinput_path_create_context();
- new Thread(() => InputThread(ctx)).Start();
+ new Thread(() => InputThread(ctx))
+ {
+ IsBackground = true
+ }.Start();
}
private unsafe void InputThread(IntPtr ctx)
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index e4a48c78fe..76e527a39a 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -125,9 +125,9 @@ namespace Avalonia.LinuxFramebuffer
_topLevel = tl;
- if (_topLevel is IFocusScope scope)
+ if (_topLevel is IFocusScope scope && _topLevel.FocusManager is FocusManager focusManager)
{
- ((FocusManager)_topLevel.FocusManager).SetFocusScope(scope);
+ focusManager.SetFocusScope(scope);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
index 939efc60d1..bb18dd5ee3 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
index f3a5a9415c..480c74223f 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
@@ -209,11 +209,34 @@ namespace Avalonia.Markup.Xaml.XamlIl
var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
+ var diagnostics = new List();
+ var diagnosticsHandler = new XamlDiagnosticsHandler()
+ {
+ HandleDiagnostic = (diagnostic) =>
+ {
+ var runtimeDiagnostic = new RuntimeXamlDiagnostic(diagnostic.Code.ToString(),
+ (RuntimeXamlDiagnosticSeverity)diagnostic.Severity,
+ diagnostic.Title, diagnostic.LineNumber, diagnostic.LinePosition)
+ {
+ Document = diagnostic.Document
+ };
+ var newSeverity =
+ (XamlDiagnosticSeverity?)configuration.DiagnosticHandler?.Invoke(runtimeDiagnostic) ??
+ diagnostic.Severity;
+ diagnostic = diagnostic with { Severity = newSeverity };
+ diagnostics.Add(diagnostic);
+ return newSeverity;
+ },
+ CodeMappings = AvaloniaXamlDiagnosticCodes.XamlXDiagnosticCodeToAvalonia
+ };
+
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
- new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
+ new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder)),
+ null,
+ diagnosticsHandler),
_sreEmitMappings,
_sreContextType)
{
@@ -236,8 +259,9 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType());
}
-
+
var parsed = compiler.Parse(xaml, overrideType);
+ parsed.Document = "runtimexaml:" + parsedDocuments.Count;
compiler.Transform(parsed);
var xamlName = GetSafeUriIdentifier(document.BaseUri)
@@ -263,6 +287,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
compiler.TransformGroup(parsedDocuments);
+ diagnostics.ThrowExceptionIfAnyError();
+
var createdTypes = parsedDocuments.Select(document =>
{
compiler.Compile(document.XamlDocument, document.TypeBuilderProvider, document.Uri, document.FileSource);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
index 339b720d10..80e1807ac3 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
@@ -22,14 +22,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes
_values = values;
Type = new XamlAstClrTypeReference(lineInfo, arrayType, false);
-
- foreach (var element in values)
- {
- if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
- {
- throw new XamlParseException("x:Array element is not assignable to the array element type!", lineInfo);
- }
- }
}
public IXamlAstTypeReference Type { get; }
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs
new file mode 100644
index 0000000000..ddc5e1c2de
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs
@@ -0,0 +1,63 @@
+using System;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
+using XamlX.Ast;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
+
+internal static class AvaloniaXamlDiagnosticCodes
+{
+ public const string Unknown = "AVLN9999";
+
+ // XML/XAML parsing errors 1000-1999.
+ public const string ParseError = "AVLN1000";
+ public const string InvalidXAML = "AVLN1001";
+
+ // XAML transform errors 2000-2999.
+ public const string TransformError = "AVLN2000";
+ public const string DuplicateXClass = "AVLN2002";
+ public const string TypeSystemError = "AVLN2003";
+ public const string AvaloniaIntrinsicsError = "AVLN2005";
+ public const string BindingsError = "AVLN2100";
+ public const string DataContextResolvingError = "AVLN2101";
+ public const string StyleTransformError = "AVLN2200";
+ public const string SelectorsTransformError = "AVLN2201";
+ public const string PropertyPathError = "AVLN2202";
+ public const string DuplicateSetterError = "AVLN2203";
+ public const string StyleInMergedDictionaries = "AVLN2204";
+
+ // XAML emit errors 3000-3999.
+ public const string EmitError = "AVLN3000";
+ public const string XamlLoaderUnreachable = "AVLN3001";
+
+ // Generator specific errors 4000-4999.
+ public const string NameGeneratorError = "AVLN4001";
+
+ // Reserved 5000-9998
+ public const string Obsolete = "AVLN5001";
+
+ internal static string XamlXDiagnosticCodeToAvalonia(object xamlException)
+ {
+ return xamlException switch
+ {
+ XamlXWellKnownDiagnosticCodes wellKnownDiagnosticCodes => wellKnownDiagnosticCodes switch
+ {
+ XamlXWellKnownDiagnosticCodes.Obsolete => Obsolete,
+ _ => throw new ArgumentOutOfRangeException()
+ },
+
+ XamlDataContextException => DataContextResolvingError,
+ XamlBindingsTransformException => BindingsError,
+ XamlPropertyPathException => PropertyPathError,
+ XamlStyleTransformException => StyleTransformError,
+ XamlSelectorsTransformException => SelectorsTransformError,
+
+ XamlTransformException => TransformError,
+ XamlTypeSystemException => TypeSystemError,
+ XamlLoadException => EmitError,
+ XamlParseException => ParseError,
+
+ _ => Unknown
+ };
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 88a0039dba..723675f975 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -57,6 +57,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
new AvaloniaXamlIlSetterTransformer(),
+ new AvaloniaXamlIlStyleValidatorTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
@@ -126,9 +127,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public List GroupTransformers { get; }
- public void TransformGroup(IReadOnlyCollection documents, bool strict = true)
+ public void TransformGroup(IReadOnlyCollection documents)
{
- var ctx = new AstGroupTransformationContext(documents, _configuration, strict);
+ var ctx = new AstGroupTransformationContext(documents, _configuration);
foreach (var transformer in GroupTransformers)
{
foreach (var doc in documents)
@@ -166,8 +167,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new XamlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
false) :
- TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
- (XamlAstXmlTypeReference)rootObject.Type, true);
+ TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed),
+ (XamlAstXmlTypeReference)rootObject.Type);
if (overrideRootType != null)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
index 9fc6b5d517..d5b760aa84 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
@@ -17,8 +17,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
XamlIlTrampolineBuilder trampolineBuilder,
- IXamlIdentifierGenerator identifierGenerator = null)
- : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator)
+ IXamlIdentifierGenerator identifierGenerator,
+ XamlDiagnosticsHandler diagnosticsHandler)
+ : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator, diagnosticsHandler)
{
ClrPropertyEmitter = clrPropertyEmitter;
AccessorFactoryEmitter = accessorFactoryEmitter;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
index f9a121443e..b11086e74d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
@@ -6,6 +6,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Media;
+using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
@@ -16,6 +17,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
public static bool TryConvert(AstTransformationContext context, IXamlAstValueNode node, string text, IXamlType type, AvaloniaXamlIlWellKnownTypes types, out IXamlAstValueNode result)
{
+ bool ReturnOnParseError(string title, out IXamlAstValueNode result)
+ {
+ context.ReportDiagnostic(new XamlDiagnostic(
+ AvaloniaXamlDiagnosticCodes.AvaloniaIntrinsicsError,
+ XamlDiagnosticSeverity.Error,
+ title,
+ node)
+ {
+ // Only one instance when we can lower Error to a Warning
+ MinSeverity = XamlDiagnosticSeverity.Warning
+ });
+ result = null;
+ return false;
+ }
+
if (type.FullName == "System.TimeSpan")
{
var tsText = text.Trim();
@@ -24,11 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
// // shorthand seconds format (ie. "0.25")
if (!tsText.Contains(":") && double.TryParse(tsText,
- NumberStyles.Float | NumberStyles.AllowThousands,
- CultureInfo.InvariantCulture, out var seconds))
+ NumberStyles.Float | NumberStyles.AllowThousands,
+ CultureInfo.InvariantCulture, out var seconds))
timeSpan = TimeSpan.FromSeconds(seconds);
else
- throw new XamlX.XamlLoadException($"Unable to parse {text} as a time span", node);
+ {
+ return ReturnOnParseError($"Unable to parse {text} as a time span", out result);
+ }
}
result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@@ -56,7 +74,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a thickness", out result);
}
}
@@ -73,7 +91,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a point", out result);
}
}
@@ -90,7 +108,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a vector", out result);
}
}
@@ -107,7 +125,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a size", out result);
}
}
@@ -124,7 +142,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a matrix", out result);
}
}
@@ -141,7 +159,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a corner radius", out result);
}
}
@@ -149,7 +167,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
if (!Color.TryParse(text, out Color color))
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a color", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a color", out result);
}
result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@@ -179,7 +197,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a relative point", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a relative point", out result);
}
}
@@ -195,7 +213,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a grid length", out result);
}
}
@@ -218,7 +236,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
+ return ReturnOnParseError($"Unable to parse \"{text}\" as a grid length", out result);
}
}
@@ -295,7 +313,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _))
{
- throw new XamlX.XamlLoadException($"Unable to parse text {uriText} as a {kind} uri.", node);
+ return ReturnOnParseError($"Unable to parse text \"{uriText}\" as a {kind} uri", out result);
}
result = new XamlAstNewClrObjectNode(node
, new(node, types.Uri, false)
@@ -341,7 +359,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
else
{
- throw new XamlX.XamlLoadException($"Invalid PointsList.", node);
+ return ReturnOnParseError($"Unable to parse text \"{text}\" as a Points list", out result);
}
}
else
@@ -388,6 +406,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
nodes[index] = itemNode;
}
+ foreach (var element in nodes)
+ {
+ if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
+ {
+ return ReturnOnParseError($"x:Array element {element.Type.GetClrType().Name} is not assignable to the array element type {elementType.Name}", out result);
+ }
+ }
+
if (types.AvaloniaList.MakeGenericType(elementType).IsAssignableFrom(type))
{
result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, elementType, nodes);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
index eeb5293325..37b1952c49 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
@@ -9,55 +9,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
internal class AstGroupTransformationContext : AstTransformationContext
{
- public AstGroupTransformationContext(IReadOnlyCollection documents, TransformerConfiguration configuration, bool strictMode = true)
- : base(configuration, null, strictMode)
+ public AstGroupTransformationContext(
+ IReadOnlyCollection documents,
+ TransformerConfiguration configuration)
+ : base(configuration, null)
{
Documents = documents;
}
+ public override string Document => CurrentDocument?.FileSource?.FilePath ?? "{unknown document}";
public IXamlDocumentResource CurrentDocument { get; set; }
public IReadOnlyCollection Documents { get; }
- public new IXamlAstNode ParseError(string message, IXamlAstNode node) =>
- Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node));
-
- public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) =>
- Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender));
-
- class Visitor : IXamlAstVisitor
+ class Visitor : ContextXamlAstVisitor
{
- private readonly AstGroupTransformationContext _context;
private readonly IXamlAstGroupTransformer _transformer;
- public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer)
+ public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) : base(context)
{
- _context = context;
_transformer = transformer;
}
-
- public IXamlAstNode Visit(IXamlAstNode node)
- {
-#if Xaml_DEBUG
- return _transformer.Transform(_context, node);
-#else
- try
- {
- return _transformer.Transform(_context, node);
- }
- catch (Exception e) when (!(e is XmlException))
- {
- throw new XamlDocumentParseException(
- _context.CurrentDocument?.FileSource?.FilePath,
- "Internal compiler error while transforming node " + node + ":\n" + e,
- node);
- }
-#endif
- }
- public void Push(IXamlAstNode node) => _context.PushParent(node);
+ public override string GetTransformerInfo() => _transformer.GetType().Name;
- public void Pop() => _context.PopParent();
+ public override IXamlAstNode VisitCore(AstTransformationContext context, IXamlAstNode node) =>
+ _transformer.Transform((AstGroupTransformationContext)context, node);
}
public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
index 28a48e5855..7c345c060f 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
@@ -38,8 +38,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode)
{
- throw new XamlDocumentParseException(context.CurrentDocument,
- $"Invalid \"{nodeTypeName}\" node initialization.", valueNode);
+ throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization.");
}
var additionalProperties = new List();
@@ -56,8 +55,9 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
}
else
{
- throw new XamlDocumentParseException(context.CurrentDocument,
+ context.ReportTransformError(
$"Source property must be set on the \"{nodeTypeName}\" node.", valueNode);
+ return node;
}
}
@@ -89,22 +89,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly, additionalProperties);
}
- return context.ParseError(
+ context.ReportTransformError(
$"Unable to resolve XAML resource \"{assetPathUri}\" in the current assembly.",
- sourceUriNode, node);
+ sourceUriNode);
+ return node;
}
// If resource wasn't found in the current assembly, search in the others.
if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
{
- return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node);
+ context.ReportTransformError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode);
+ return node;
}
var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
if (avaResType is null)
{
- return context.ParseError(
- $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node);
+ context.ReportTransformError(
+ $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode);
+ return node;
}
var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
@@ -118,20 +121,22 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
return FromType(context, type, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly, additionalProperties);
}
- return context.ParseError(
+ context.ReportTransformError(
$"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly. Make sure this file exists and is public.",
- sourceUriNode, node);
+ sourceUriNode);
+ return node;
}
- private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li,
+ private static IXamlAstNode FromType(AstGroupTransformationContext context, IXamlType type, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
IEnumerable manipulationNodes)
{
if (!expectedLoadedType.IsAssignableFrom(type))
{
- return context.ParseError(
+ context.ReportTransformError(
$"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
- li, fallbackNode);
+ li);
+ return fallbackNode;
}
IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
@@ -141,15 +146,16 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
return new NewObjectTransformer().Transform(context, newObjNode);
}
- private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlAstNode li,
+ private static IXamlAstNode FromMethod(AstGroupTransformationContext context, IXamlMethod method, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
IEnumerable manipulationNodes)
{
if (!expectedLoadedType.IsAssignableFrom(method.ReturnType))
{
- return context.ParseError(
+ context.ReportTransformError(
$"Resource \"{assetPathUri}\" is defined as \"{method.ReturnType}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
- li, fallbackNode);
+ li);
+ return fallbackNode;
}
var sp = context.Configuration.TypeMappings.ServiceProvider;
@@ -164,6 +170,13 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty,
bool strictSourceValueType)
{
+ void OnInvalidSource(IXamlAstNode? node) =>
+ context.ReportDiagnostic(
+ AvaloniaXamlDiagnosticCodes.TransformError,
+ strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
+ $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
+ node);
+
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
@@ -172,16 +185,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
// Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
var anyPropValue = sourceProperty.Values.FirstOrDefault();
- if (strictSourceValueType)
- {
- context.Error(anyPropValue,
- new XamlDocumentParseException(context.CurrentDocument,
- $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", anyPropValue));
- }
- else
- {
- // TODO: make it a compiler warning
- }
+ OnInvalidSource(anyPropValue);
return (null, anyPropValue);
}
@@ -193,9 +197,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
}
else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
{
- context.Error(sourceUriNode,
- new XamlDocumentParseException(context.CurrentDocument,
- $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", sourceUriNode));
+ OnInvalidSource(sourceUriNode);
return (null, sourceUriNode);
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
index f135ed5ffb..61a12aa72d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
using XamlX.Ast;
using XamlX.TypeSystem;
@@ -22,6 +23,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
var mergeSourceNodes = new List();
+ var shouldExit = false; // if any manipulation node has an error, we should stop processing further.
foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
{
void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
@@ -39,14 +41,16 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
}
else
{
- throw new XamlDocumentParseException(context.CurrentDocument,
+ shouldExit = true;
+ context.ReportTransformError(
"Invalid MergeResourceInclude node found. Make sure that Source property is set.",
valueNode);
}
}
else if (mergeSourceNodes.Any())
{
- throw new XamlDocumentParseException(context.CurrentDocument,
+ shouldExit = true;
+ context.ReportTransformError(
"MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.",
valueNode);
}
@@ -66,7 +70,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
}
}
- if (!mergeSourceNodes.Any())
+ if (shouldExit || !mergeSourceNodes.Any())
{
return node;
}
@@ -78,7 +82,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true);
if (originalAssetPath is null)
{
- return context.ParseError(
+ return context.ReportTransformError(
$"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
}
@@ -86,7 +90,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase));
if (targetDocument?.XamlDocument.Root is not XamlValueWithManipulationNode targetDocumentRoot)
{
- return context.ParseError(
+ return context.ReportTransformError(
$"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
}
@@ -94,7 +98,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
.Children.OfType().Single();
if (singleRootObject.Type != resourceDictionaryType)
{
- return context.ParseError(
+ return context.ReportTransformError(
"MergeResourceInclude can only include another ResourceDictionary", propertyNode, node);
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
index de2c0eab96..e530b39a65 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
@@ -1,3 +1,4 @@
+using System;
using System.Linq;
using XamlX;
using XamlX.Ast;
@@ -8,6 +9,14 @@ using XamlParseException = XamlX.XamlParseException;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
+ class XamlBindingsTransformException : XamlTransformException
+ {
+ public XamlBindingsTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+ : base(message, lineInfo, innerException)
+ {
+ }
+ }
+
class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
{
public bool CompileBindingsByDefault { get; set; }
@@ -32,7 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (!(directive.Values[0] is XamlAstTextNode text
&& bool.TryParse(text.Text, out var compileBindings)))
{
- throw new XamlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
+ throw new XamlBindingsTransformException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
}
obj.Children.Remove(directive);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs
index ed42247d29..f586722563 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs
@@ -11,12 +11,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
///
/// Converts the property assignment `Classes="foo bar"` to:
///
- ///
+ ///
- /// foo
- /// bar
+ /// foo
+ /// bar
///
- ///
+ /// ]]>
///
class AvaloniaXamlIlClassesTransformer : IXamlAstTransformer
{
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
index 736e764aa7..b62066e916 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
@@ -104,12 +104,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else if (elementNameProperty != null)
{
- throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
+ throw new XamlBindingsTransformException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
}
if (sourceProperty != null && convertedNode != null)
{
- throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+ throw new XamlBindingsTransformException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
if (GetRelativeSourceObjectFromAssignment(
@@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (convertedNode != null)
{
- throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+ throw new XamlBindingsTransformException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
var modeProperty = relativeSourceObject.Children
@@ -159,14 +159,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
true).GetClrType(),
XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
null => null,
- _ => throw new XamlParseException($"Unsupported node for AncestorType property", relativeSourceObject)
+ _ => throw new XamlBindingsTransformException($"Unsupported node for AncestorType property", relativeSourceObject)
};
if (ancestorType is null)
{
if (treeType == "Visual")
{
- throw new XamlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
+ throw new XamlBindingsTransformException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
}
else if (treeType == "Logical")
{
@@ -180,7 +180,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (ancestorType is null)
{
- throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
+ throw new XamlBindingsTransformException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
}
}
}
@@ -203,7 +203,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else
{
- throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
+ throw new XamlBindingsTransformException($"Unknown tree type '{treeType}'.", binding);
}
}
else if (mode == "DataContext")
@@ -221,20 +221,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
if (contentTemplateNode is null)
{
- throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
+ throw new XamlBindingsTransformException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
}
var parentType = contentTemplateNode.TargetType.GetClrType();
if (parentType is null)
{
- throw new XamlParseException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
+ throw new XamlBindingsTransformException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
}
convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
}
else
{
- throw new XamlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
+ throw new XamlBindingsTransformException($"Unknown RelativeSource mode '{mode}'.", binding);
}
}
@@ -265,7 +265,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
{
- throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
+ throw new XamlBindingsTransformException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
}
relativeSourceObject = (XamlAstObjectNode)me.Value;
@@ -276,7 +276,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
{
- throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
+ throw new XamlBindingsTransformException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
}
relativeSourceObject = on;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
index 3edc458fd2..fa697972e9 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
@@ -116,7 +116,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault();
if (parentDataContextNode is null)
{
- throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
+ throw new XamlBindingsTransformException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
}
return parentDataContextNode.DataContextType;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
index 51fe58d1c9..09b6779fa3 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var targetTypeNode = on.Children.OfType()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ??
- throw new XamlParseException("ControlTheme must have a TargetType.", node);
+ throw new XamlTransformException("ControlTheme must have a TargetType.", node);
IXamlType targetType;
@@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (targetTypeNode.Values[0] is XamlAstTextNode text)
targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
else
- throw new XamlParseException("Could not determine TargetType for ControlTheme.", targetTypeNode);
+ throw new XamlTransformException("Could not determine TargetType for ControlTheme.", targetTypeNode);
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(targetTypeNode, targetType, false),
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index a24d4eb6e9..488fcef46c 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -10,6 +10,14 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
+ class XamlDataContextException : XamlTransformException
+ {
+ public XamlDataContextException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+ : base(message, lineInfo, innerException)
+ {
+ }
+ }
+
class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -43,7 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else
{
- throw new XamlX.XamlParseException("x:DataType should be set to a type name.", directive.Values[0]);
+ throw new XamlDataContextException("x:DataType should be set to a type name.", directive.Values[0]);
}
}
}
@@ -136,7 +144,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType().FirstOrDefault();
if (parentItemsDataContext != null)
{
- itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, () => parentItemsDataContext.DataContextType, parentObject.Type.GetClrType());
+ itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context,
+ parentItemsBinding, () => parentItemsDataContext.DataContextType,
+ parentObject.Type.GetClrType());
}
}
}
@@ -179,13 +189,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault();
if (parentDataContextNode is null)
{
- throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
+ throw new XamlDataContextException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
}
return parentDataContextNode.DataContextType;
};
- var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver, on.Type.GetClrType());
+ var bindingResultType =
+ XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver,
+ on.Type.GetClrType());
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType);
}
@@ -222,6 +234,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
}
- public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element. Please set x:DataType on the Binding or parent.", Value);
+ public override IXamlType DataContextType => XamlPseudoType.Unknown;
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
index 4ab9594cd8..c019ca6853 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
@@ -36,7 +36,10 @@ class AvaloniaXamlIlDuplicateSettersChecker : IXamlAstTransformer
{
if (!index.Add(property))
{
- throw new XamlParseException($"Duplicate setter encountered for property '{property}'", node);
+ context.ReportDiagnostic(new XamlDiagnostic(
+ AvaloniaXamlDiagnosticCodes.DuplicateSetterError,
+ XamlDiagnosticSeverity.Warning,
+ $"Duplicate setter encountered for property '{property}'", node));
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
index 13de455b96..06db71f3eb 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
@@ -38,7 +38,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
{
if (objectNode.Arguments.Count > 1)
{
- throw new XamlParseException("Options MarkupExtensions allow only single argument", objectNode);
+ throw new XamlTransformException("Options MarkupExtensions allow only single argument", objectNode);
}
defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
@@ -68,20 +68,20 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
?? Array.Empty();
if (options.Length == 0)
{
- throw new XamlParseException("On.Options string must be set", onObj);
+ throw new XamlTransformException("On.Options string must be set", onObj);
}
var content = onObj.Children.OfType()
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
if (content is null)
{
- throw new XamlParseException("On content object must be set", onObj);
+ throw new XamlTransformException("On content object must be set", onObj);
}
var propertiesSet = options
.Select(o => type.GetAllProperties()
.FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
- ?? throw new XamlParseException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
+ ?? throw new XamlTransformException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
.ToArray();
foreach (var propertySet in propertiesSet)
{
@@ -102,7 +102,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
if (defaultValue is null && !values.Any())
{
- throw new XamlParseException("Options markup extension requires at least one option to be set", objectNode);
+ throw new XamlTransformException("Options markup extension requires at least one option to be set", objectNode);
}
return new OptionsMarkupExtensionNode(
@@ -125,7 +125,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
var option = optAttr.Parameters.Single();
if (option is null)
{
- throw new XamlParseException("MarkupExtension option must not be null", li);
+ throw new XamlTransformException("MarkupExtension option must not be null", li);
}
var optionAsString = option.ToString();
@@ -173,7 +173,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
}
}
- throw new XamlParseException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
+ throw new XamlTransformException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
}
return false;
@@ -198,7 +198,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
if (values.Count > 1)
{
- throw new XamlParseException("Options markup extension supports only a singular value", line);
+ throw new XamlTransformException("Options markup extension supports only a singular value", line);
}
return values.Single();
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
index 44b469c34e..067e01e671 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
@@ -12,6 +12,14 @@ using XamlX.IL;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
+ class XamlPropertyPathException : XamlTransformException
+ {
+ public XamlPropertyPathException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+ : base(message, lineInfo, innerException)
+ {
+ }
+ }
+
class AvaloniaXamlIlPropertyPathTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -26,9 +34,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentScope = context.ParentNodes().OfType()
.FirstOrDefault();
if(parentScope == null)
- throw new XamlX.XamlParseException("No target type scope found for property path", text);
+ throw new XamlPropertyPathException("No target type scope found for property path", text);
if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
- throw new XamlX.XamlParseException("PropertyPath is currently only valid for styles", pv);
+ throw new XamlPropertyPathException("PropertyPath is currently only valid for styles", pv);
IEnumerable parsed;
@@ -38,7 +46,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
- throw new XamlX.XamlParseException("Unable to parse PropertyPath: " + e.Message, text);
+ throw new XamlPropertyPathException("Unable to parse PropertyPath: " + e.Message, text, innerException: e);
}
var elements = new List();
@@ -59,7 +67,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
void HandleProperty(string name, string typeNamespace, string typeName)
{
if(!expectProperty || currentType == null)
- throw new XamlX.XamlParseException("Unexpected property node", text);
+ throw new XamlPropertyPathException("Unexpected property node", text);
var propertySearchType =
typeName != null ? GetType(typeNamespace, typeName) : currentType;
@@ -80,7 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
if (prop == null)
- throw new XamlX.XamlParseException(
+ throw new XamlPropertyPathException(
$"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
text);
@@ -95,7 +103,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (ge is PropertyPathGrammar.ChildTraversalSyntax)
{
if (!expectTraversal)
- throw new XamlX.XamlParseException("Unexpected child traversal .", text);
+ throw new XamlPropertyPathException("Unexpected child traversal .", text);
elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
expectTraversal = expectCast = false;
expectProperty = true;
@@ -103,7 +111,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
{
if(!expectCast)
- throw new XamlX.XamlParseException("Unexpected cast node", text);
+ throw new XamlPropertyPathException("Unexpected cast node", text);
currentType = GetType(ets.TypeNamespace, ets.TypeName);
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
expectProperty = false;
@@ -112,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
{
if(!expectCast)
- throw new XamlX.XamlParseException("Unexpected cast node", text);
+ throw new XamlPropertyPathException("Unexpected cast node", text);
//TODO: Check if cast can be done
currentType = GetType(cts.TypeNamespace, cts.TypeName);
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
@@ -128,12 +136,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
}
else
- throw new XamlX.XamlParseException("Unexpected node " + ge, text);
+ throw new XamlPropertyPathException("Unexpected node " + ge, text);
}
var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
if (propertyPathNode.Type == null)
- throw new XamlX.XamlParseException("Unexpected end of the property path", text);
+ throw new XamlPropertyPathException("Unexpected end of the property path", text);
pv.Values[0] = propertyPathNode;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
index edddc5424a..5292aadab2 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
@@ -13,8 +13,14 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- using XamlParseException = XamlX.XamlParseException;
- using XamlLoadException = XamlX.XamlLoadException;
+ internal class XamlSelectorsTransformException : XamlTransformException
+ {
+ public XamlSelectorsTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+ : base(message, lineInfo, innerException)
+ {
+ }
+ }
+
class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -30,14 +36,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
if (pn.Values.Count != 1)
- throw new XamlParseException("Selector property should should have exactly one value", node);
+ throw new XamlSelectorsTransformException("Selector property should should have exactly one value",
+ node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlAstTextNode tn))
- throw new XamlParseException("Selector property should be a text node", node);
+ throw new XamlSelectorsTransformException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
@@ -69,18 +76,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var type = result?.TargetType;
if (type == null)
- throw new XamlParseException("Property selectors must be applied to a type.", node);
+ throw new XamlTransformException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
- throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
+ throw new XamlTransformException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
- throw new XamlParseException(
+ throw new XamlTransformException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
@@ -92,13 +99,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var targetType = result?.TargetType;
if (targetType == null)
{
- throw new XamlParseException("Attached Property selectors must be applied to a type.",node);
+ throw new XamlTransformException("Attached Property selectors must be applied to a type.",node);
}
var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
if (attachedPropertyOwnerType is null)
{
- throw new XamlParseException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
+ throw new XamlTransformException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
}
var attachedPropertyName = attachedProperty.Property + "Property";
@@ -112,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (targetPropertyField is null)
{
- throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
+ throw new XamlTransformException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
}
var targetPropertyType = XamlIlAvaloniaPropertyHelper
@@ -121,7 +128,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
targetPropertyType, out var typedValue))
- throw new XamlParseException(
+ throw new XamlTransformException(
$"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
node);
@@ -156,12 +163,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentTargetType = context.ParentNodes().OfType().FirstOrDefault();
if (parentTargetType is null)
- throw new XamlParseException($"Cannot find parent style for nested selector.", node);
+ throw new XamlTransformException($"Cannot find parent style for nested selector.", node);
result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
break;
default:
- throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
+ throw new XamlTransformException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
@@ -180,11 +187,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
- throw new XamlParseException("Unable to parse selector: " + e.Message, node);
+ throw new XamlSelectorsTransformException("Unable to parse selector: " + e.Message, node, e);
}
var selector = Create(parsed, (p, n)
- => TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
+ => TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true)
+ ?? new XamlAstClrTypeReference(node, XamlPseudoType.Unknown, false));
pn.Values[0] = selector;
var templateType = GetLastTemplateTypeFromSelector(selector);
@@ -402,7 +410,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
- throw new XamlLoadException(
+ throw new XamlX.XamlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
@@ -486,7 +494,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
if (_selectors.Count == 0)
- throw new XamlLoadException("Invalid selector count", this);
+ throw new XamlX.XamlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
index ebc6c01ba8..07a10f0875 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
@@ -25,7 +25,7 @@ internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTrans
if (type is null)
{
- throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective);
+ throw new XamlTransformException("Unable to resolve SetterTargetType type", typeDirective);
}
return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
index 5a6fd8246d..8b5a893884 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using XamlX;
@@ -9,6 +10,14 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
+ class XamlStyleTransformException : XamlTransformException
+ {
+ public XamlStyleTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+ : base(message, lineInfo, innerException)
+ {
+ }
+ }
+
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -27,13 +36,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (styleParent != null)
{
targetType = styleParent.TargetType.GetClrType()
- ?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
+ ?? throw new XamlStyleTransformException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
lineInfo = on;
}
if (targetType == null)
{
- throw new XamlParseException("Could not determine target type of Setter", node);
+ throw new XamlStyleTransformException("Could not determine target type of Setter", node);
}
IXamlType propType = null;
@@ -43,7 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
var propertyName = property.Values.OfType().FirstOrDefault()?.Text;
if (propertyName == null)
- throw new XamlParseException("Setter.Property must be a string", node);
+ throw new XamlStyleTransformException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
@@ -55,12 +64,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var propertyPath = on.Children.OfType()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
if (propertyPath == null)
- throw new XamlX.XamlParseException("Setter without a property or property path is not valid", node);
+ throw new XamlStyleTransformException("Setter without a property or property path is not valid", node);
if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
&& ppn.PropertyType != null)
propType = ppn.PropertyType;
else
- throw new XamlX.XamlParseException("Unable to get the property path property type", node);
+ throw new XamlStyleTransformException("Unable to get the property path property type", node);
}
var valueProperty = on.Children
@@ -69,7 +78,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted))
- throw new XamlParseException(
+ throw new XamlStyleTransformException(
$"Unable to convert property value to {propType.GetFqn()}",
valueProperty.Values[0]);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs
new file mode 100644
index 0000000000..87528649e6
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs
@@ -0,0 +1,34 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+internal class AvaloniaXamlIlStyleValidatorTransformer : IXamlAstTransformer
+{
+ // See https://github.com/AvaloniaUI/Avalonia/issues/7461
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!(node is XamlAstObjectNode on
+ && context.GetAvaloniaTypes().IStyle.IsAssignableFrom(on.Type.GetClrType())))
+ return node;
+
+ if (context.ParentNodes().FirstOrDefault() is XamlAstXamlPropertyValueNode propertyValueNode
+ && propertyValueNode.Property.GetClrProperty() is { } clrProperty
+ && clrProperty.Name == "MergedDictionaries"
+ && clrProperty.DeclaringType == context.GetAvaloniaTypes().ResourceDictionary)
+ {
+ var nodeName = on.Type.GetClrType().Name;
+ context.ReportDiagnostic(new XamlDiagnostic(
+ AvaloniaXamlDiagnosticCodes.StyleInMergedDictionaries,
+ XamlDiagnosticSeverity.Warning,
+ // Keep it single line, as MSBuild splits multiline warnings into two warnings.
+ $"Including {nodeName} as part of MergedDictionaries will ignore any nested styles." +
+ $"Instead, you can add {nodeName} to the Styles collection on the same control or application.",
+ node));
+ }
+
+ return node;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
deleted file mode 100644
index 0532287a67..0000000000
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using XamlX;
-using XamlX.Ast;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
-
-internal class XamlDocumentParseException : XamlParseException
-{
- public string FilePath { get; }
-
- public XamlDocumentParseException(string path, XamlParseException parseException)
- : base(parseException.Message, parseException.LineNumber, parseException.LinePosition)
- {
- FilePath = path;
- }
-
- public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo)
- : base(message, lineInfo.Line, lineInfo.Position)
- {
- FilePath = path;
- }
-
- public XamlDocumentParseException(IXamlDocumentResource document, string message, IXamlLineInfo lineInfo)
- : this(document.FileSource?.FilePath, message, lineInfo)
- {
- }
-}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
index 264361e743..5ae8a8f182 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
@@ -83,7 +83,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var found = tref.Type.GetAllFields()
.FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
if (found == null)
- throw new XamlX.XamlParseException(
+ throw new XamlX.XamlTransformException(
$"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
}
@@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
- throw new XamlX.XamlParseException(
+ throw new XamlX.XamlTransformException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty, make sure to use typed properties",
lineInfo);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
index bd8de09a12..f8b61992ba 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
@@ -88,8 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return transformed;
}
- var lastElement =
- transformed.Elements[transformed.Elements.Count - 1];
+ var lastElement = transformed.Elements.LastOrDefault();
if (parentNode.Property?.Getter?.ReturnType == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
{
@@ -159,7 +158,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
break;
}
- throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
+ throw new XamlX.XamlTransformException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
}
case BindingExpressionGrammar.PropertyNameNode propName:
{
@@ -183,7 +182,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
else
{
- throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
+ throw new XamlX.XamlTransformException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
}
break;
}
@@ -208,7 +207,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
if (property is null)
{
- throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
+ throw new XamlX.XamlTransformException($"The type '${targetType}' does not have an indexer.", lineInfo);
}
IEnumerable parameters = property.IndexerParameters;
@@ -220,7 +219,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
param, out var converted))
- throw new XamlX.XamlParseException(
+ throw new XamlX.XamlTransformException(
$"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
textNode);
@@ -274,7 +273,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (ancestorType is null)
{
- throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
+ throw new XamlX.XamlTransformException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
}
nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
@@ -301,7 +300,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (elementType is null)
{
- throw new XamlX.XamlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
+ throw new XamlX.XamlTransformException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
}
nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
break;
@@ -313,7 +312,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (castType is null)
{
- throw new XamlX.XamlParseException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
+ throw new XamlX.XamlTransformException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
}
nodes.Add(new TypeCastPathElementNode(castType));
@@ -808,7 +807,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
if (!int.TryParse(item, out var index))
{
- throw new XamlX.XamlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
+ throw new XamlX.XamlTransformException($"Unable to convert '{item}' to an integer.", lineInfo);
}
_values.Add(index);
}
@@ -889,10 +888,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Elements = elements;
}
- public IXamlType BindingResultType
- => _transformElements.Count > 0
- ? _transformElements[0].Type
- : Elements[Elements.Count - 1].Type;
+ public IXamlType BindingResultType =>
+ _transformElements.FirstOrDefault()?.Type
+ ?? Elements.LastOrDefault()?.Type
+ ?? XamlPseudoType.Unknown;
public IXamlAstTypeReference Type { get; }
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props b/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
index c902fa956a..251f84d304 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
@@ -5,8 +5,5 @@
-
-
-
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
index aa2223dec1..b7ed273273 160000
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
@@ -1 +1 @@
-Subproject commit aa2223dec1e7c70679fdb73f9d364363a0285adb
+Subproject commit b7ed273273949a5dd9f01e682ab97f61b43697ad
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 6ec9203f3f..b897fed046 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -109,6 +109,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
index 1cd48f87d7..90e607ddcc 100644
--- a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
@@ -1,3 +1,4 @@
+using System;
using System.Reflection;
namespace Avalonia.Markup.Xaml;
@@ -20,4 +21,50 @@ public class RuntimeXamlLoaderConfiguration
/// Default is 'false'.
///
public bool DesignMode { get; set; } = false;
+
+ ///
+ /// XAML diagnostics handler.
+ ///
+ ///
+ /// Defines if any diagnostic severity should be overriden.
+ /// Note, severity cannot be set lower than minimal for specific diagnostic.
+ ///
+ public XamlDiagnosticFunc? DiagnosticHandler { get; set; }
+
+ ///
+ /// Delegate for property.
+ ///
+ public delegate RuntimeXamlDiagnosticSeverity XamlDiagnosticFunc(RuntimeXamlDiagnostic diagnostic);
+}
+
+public enum RuntimeXamlDiagnosticSeverity
+{
+ None = 0,
+
+ ///
+ /// Diagnostic is reported as a warning.
+ ///
+ Warning = 1,
+
+ ///
+ /// Diagnostic is reported as an error.
+ /// Compilation process is continued until the end of the parsing and transforming stage, throwing an aggregated exception of all errors.
+ ///
+ Error,
+
+ ///
+ /// Diagnostic is reported as an fatal error.
+ /// Compilation process is stopped right after this error.
+ ///
+ Fatal
+}
+
+public record RuntimeXamlDiagnostic(
+ string Id,
+ RuntimeXamlDiagnosticSeverity Severity,
+ string Title,
+ int? LineNumber,
+ int? LinePosition)
+{
+ public string? Document { get; init; }
}
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index 6051985cea..ac5e9640a7 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -161,13 +161,13 @@ namespace Avalonia.Skia
///
public void Save(string fileName, int? quality = null)
{
- ImageSavingHelper.SaveImage(_image, fileName);
+ ImageSavingHelper.SaveImage(_image, fileName, quality);
}
///
public void Save(Stream stream, int? quality = null)
{
- ImageSavingHelper.SaveImage(_image, stream);
+ ImageSavingHelper.SaveImage(_image, stream, quality);
}
///
diff --git a/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj b/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
index a6c9cb652c..ec843a2875 100644
--- a/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
+++ b/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
@@ -1,7 +1,7 @@
- net6.0-tizen
+ net6.0-tizen
enable
enable
true
diff --git a/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs b/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
index 485d9bee79..3e8abf2e54 100644
--- a/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
+++ b/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
@@ -3,11 +3,9 @@ using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.TextInput;
-using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server;
-using Avalonia.Threading;
using Tizen.NUI;
using Tizen.NUI.BaseComponents;
@@ -22,21 +20,32 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
private readonly NuiTouchHandler _touchHandler;
private readonly NuiAvaloniaViewTextEditable _textEditor;
private TizenRenderTimer? _renderTimer;
- private TopLevelImpl _topLevelImpl;
- private EmbeddableControlRoot _topLevel;
- private TouchDevice _device = new();
- private ServerCompositionTarget _compositionTargetServer;
+ private TopLevelImpl? _topLevelImpl;
+ private EmbeddableControlRoot? _topLevel;
+ private readonly TouchDevice _device = new();
+ private ServerCompositionTarget? _compositionTargetServer;
+ private IInputRoot? _inputRoot;
- public IInputRoot InputRoot { get; set; }
public INativeControlHostImpl NativeControlHost { get; }
- internal TopLevelImpl TopLevelImpl => _topLevelImpl;
public double Scaling => 1;
public Size ClientSize => new(Size.Width, Size.Height);
+ public IInputRoot InputRoot
+ {
+ get => _inputRoot ?? throw new InvalidOperationException($"{nameof(InputRoot)} hasn't been set");
+ set => _inputRoot = value;
+ }
+
+ private TopLevel TopLevel
+ => _topLevel ?? throw new InvalidOperationException($"{nameof(NuiAvaloniaView)} hasn't been initialized");
+
+ internal TopLevelImpl TopLevelImpl
+ => _topLevelImpl ?? throw new InvalidOperationException($"{nameof(NuiAvaloniaView)} hasn't been initialized");
+
public Control? Content
{
- get => _topLevel.Content as Control;
- set => _topLevel.Content = value;
+ get => TopLevel.Content as Control;
+ set => TopLevel.Content = value;
}
internal NuiAvaloniaViewTextEditable TextEditor => _textEditor;
@@ -44,7 +53,7 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
#region Setup
- public event Action OnSurfaceInit;
+ public event Action? OnSurfaceInit;
public NuiAvaloniaView() : base(ColorFormat.RGBA8888)
{
@@ -73,7 +82,7 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
private int GlRenderFrame()
{
- if (_renderTimer == null || _topLevel == null)
+ if (_renderTimer == null || _compositionTargetServer == null)
return 0;
var rev = _compositionTargetServer.Revision;
@@ -141,13 +150,13 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
private bool OnTouchEvent(object source, TouchEventArgs e)
{
- _touchHandler?.Handle(e);
+ _touchHandler.Handle(e);
return true;
}
private bool OnWheelEvent(object source, WheelEventArgs e)
{
- _touchHandler?.Handle(e);
+ _touchHandler.Handle(e);
return true;
}
@@ -169,9 +178,9 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
{
if (disposing)
{
- _topLevel.StopRendering();
- _topLevel.Dispose();
- _topLevelImpl.Dispose();
+ _topLevel?.StopRendering();
+ _topLevel?.Dispose();
+ _topLevelImpl?.Dispose();
_device.Dispose();
}
base.Dispose(disposing);
diff --git a/src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs b/src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs
index da7b57e378..c3e7577674 100644
--- a/src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs
+++ b/src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs
@@ -20,9 +20,7 @@ internal class NuiAvaloniaViewTextEditable
public bool IsActive => _client != null && _keyboardPresented;
-#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public NuiAvaloniaViewTextEditable(NuiAvaloniaView avaloniaView)
-#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
_avaloniaView = avaloniaView;
_singleLineTextInput = new NuiSingleLineTextInput
diff --git a/src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs b/src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs
index db48c626ca..d101079de5 100644
--- a/src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs
+++ b/src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs
@@ -54,7 +54,7 @@ class GlContext : IGlContext
public bool IsSharedWith(IGlContext context) => true;
public bool CanCreateSharedContext => true;
- public IGlContext CreateSharedContext(IEnumerable preferredVersions = null)
+ public IGlContext CreateSharedContext(IEnumerable? preferredVersions = null)
{
return this;
}
@@ -78,7 +78,7 @@ class GlContext : IGlContext
}
}
- public object TryGetFeature(Type featureType) => null;
+ public object? TryGetFeature(Type featureType) => null;
public IntPtr GetProcAddress(string procName)
{
diff --git a/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs b/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
index c35da77e10..105bce26bd 100644
--- a/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
+++ b/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
@@ -3,7 +3,6 @@ using Avalonia.Controls.ApplicationLifetimes;
using Tizen.NUI;
using Window = Tizen.NUI.Window;
using Avalonia.Logging;
-using Avalonia.Threading;
namespace Avalonia.Tizen;
@@ -23,7 +22,7 @@ public class NuiTizenApplication : NUIApplication
View = view;
}
- public Control MainView
+ public Control? MainView
{
get => View.Content;
set => View.Content = value;
@@ -37,9 +36,7 @@ public class NuiTizenApplication : NUIApplication
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Creating application");
base.OnCreate();
-#pragma warning disable CS8601 // Possible null reference assignment.
- TizenThreadingInterface.MainloopContext = SynchronizationContext.Current;
-#pragma warning restore CS8601 // Possible null reference assignment.
+ TizenThreadingInterface.MainloopContext = SynchronizationContext.Current!;
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup view");
_lifetime = new SingleViewLifetime(new NuiAvaloniaView());
@@ -50,7 +47,7 @@ public class NuiTizenApplication : NUIApplication
Window.Instance.RenderingBehavior = RenderingBehaviorType.Continuously;
Window.Instance.GetDefaultLayer().Add(_lifetime.View);
- Window.Instance.KeyEvent += (s, e) => _lifetime?.View?.KeyboardHandler.Handle(e);
+ Window.Instance.KeyEvent += (_, e) => _lifetime?.View.KeyboardHandler.Handle(e);
}
private void ContinueSetupApplication()
@@ -64,10 +61,10 @@ public class NuiTizenApplication : NUIApplication
{
CustomizeAppBuilder(builder);
- builder.AfterSetup(_ => _lifetime.View.Initialise());
+ builder.AfterSetup(_ => _lifetime!.View.Initialise());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup lifetime");
- builder.SetupWithLifetime(_lifetime);
+ builder.SetupWithLifetime(_lifetime!);
}, null);
}
}
diff --git a/src/Tizen/Avalonia.Tizen/Platform/Permissions.cs b/src/Tizen/Avalonia.Tizen/Platform/Permissions.cs
index 5a540d9317..a108568ff2 100644
--- a/src/Tizen/Avalonia.Tizen/Platform/Permissions.cs
+++ b/src/Tizen/Avalonia.Tizen/Platform/Permissions.cs
@@ -1,5 +1,4 @@
using System.Security;
-using System.Security.Permissions;
using Tizen.Applications;
using Tizen.Security;
@@ -33,7 +32,7 @@ internal class Permissions
}
}
- public static bool IsPrivilegeDeclared(string tizenPrivilege)
+ public static bool IsPrivilegeDeclared(string? tizenPrivilege)
{
var tizenPrivileges = tizenPrivilege;
@@ -48,21 +47,21 @@ internal class Permissions
return true;
}
- public static void EnsureDeclared(params Privilege[] requiredPrivileges)
+ public static void EnsureDeclared(params Privilege[]? requiredPrivileges)
{
if (requiredPrivileges?.Any() != true)
return;
- foreach (var (tizenPrivilege, isRuntime) in requiredPrivileges)
+ foreach (var (tizenPrivilege, _) in requiredPrivileges)
{
if (!IsPrivilegeDeclared(tizenPrivilege))
throw new SecurityException($"You need to declare the privilege: `{tizenPrivilege}` in your tizen-manifest.xml");
}
}
- public static Task CheckPrivilegeAsync(params Privilege[] requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, false);
- public static Task RequestPrivilegeAsync(params Privilege[] requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, true);
- private static async Task CheckPrivilegeAsync(Privilege[] requiredPrivileges, bool ask)
+ public static Task CheckPrivilegeAsync(params Privilege[]? requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, false);
+ public static Task RequestPrivilegeAsync(params Privilege[]? requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, true);
+ private static async Task CheckPrivilegeAsync(Privilege[]? requiredPrivileges, bool ask)
{
var ret = global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile);
if (!ret || (ret && (!profile.Equals("mobile") || !profile.Equals("wearable"))))
@@ -77,7 +76,7 @@ internal class Permissions
var tizenPrivileges = requiredPrivileges.Where(p => p.IsRuntime);
- foreach (var (tizenPrivilege, isRuntime) in tizenPrivileges)
+ foreach (var (tizenPrivilege, _) in tizenPrivileges)
{
var checkResult = PrivacyPrivilegeManager.CheckPermission(tizenPrivilege);
if (checkResult == CheckResult.Ask)
@@ -87,16 +86,21 @@ internal class Permissions
var tcs = new TaskCompletionSource();
PrivacyPrivilegeManager.GetResponseContext(tizenPrivilege)
.TryGetTarget(out var context);
- void OnResponseFetched(object sender, RequestResponseEventArgs e)
+
+ void OnResponseFetched(object? sender, RequestResponseEventArgs e)
{
tcs.TrySetResult(e.result == RequestResult.AllowForever);
}
- context.ResponseFetched += OnResponseFetched;
- PrivacyPrivilegeManager.RequestPermission(tizenPrivilege);
- var result = await tcs.Task;
- context.ResponseFetched -= OnResponseFetched;
- if (result)
- continue;
+
+ if (context != null)
+ {
+ context.ResponseFetched += OnResponseFetched;
+ PrivacyPrivilegeManager.RequestPermission(tizenPrivilege);
+ var result = await tcs.Task;
+ context.ResponseFetched -= OnResponseFetched;
+ if (result)
+ continue;
+ }
}
return false;
}
diff --git a/src/Tizen/Avalonia.Tizen/Stubs.cs b/src/Tizen/Avalonia.Tizen/Stubs.cs
index e8c93de1dc..f3f6f6f148 100644
--- a/src/Tizen/Avalonia.Tizen/Stubs.cs
+++ b/src/Tizen/Avalonia.Tizen/Stubs.cs
@@ -9,7 +9,7 @@ internal class WindowingPlatformStub : IWindowingPlatform
public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
- public ITrayIconImpl CreateTrayIcon() => null;
+ public ITrayIconImpl? CreateTrayIcon() => null;
}
internal class PlatformIconLoaderStub : IPlatformIconLoader
diff --git a/src/Tizen/Avalonia.Tizen/TizenPlatform.cs b/src/Tizen/Avalonia.Tizen/TizenPlatform.cs
index 5085994137..c3824a04ad 100644
--- a/src/Tizen/Avalonia.Tizen/TizenPlatform.cs
+++ b/src/Tizen/Avalonia.Tizen/TizenPlatform.cs
@@ -1,21 +1,33 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
-using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Tizen.Platform.Input;
using Avalonia.Tizen.Platform;
-using Avalonia.Threading;
namespace Avalonia.Tizen;
internal class TizenPlatform
{
public static readonly TizenPlatform Instance = new();
- internal static NuiGlPlatform GlPlatform { get; set; }
- internal static Compositor Compositor { get; private set; }
- internal static TizenThreadingInterface ThreadingInterface { get; private set; } = new();
+
+ private static NuiGlPlatform? s_glPlatform;
+ private static Compositor? s_compositor;
+
+ internal static NuiGlPlatform GlPlatform
+ {
+ get => s_glPlatform ?? throw new InvalidOperationException($"{nameof(TizenPlatform)} hasn't been initialized");
+ private set => s_glPlatform = value;
+ }
+
+ internal static Compositor Compositor
+ {
+ get => s_compositor ?? throw new InvalidOperationException($"{nameof(TizenPlatform)} hasn't been initialized");
+ private set => s_compositor = value;
+ }
+
+ internal static TizenThreadingInterface ThreadingInterface { get; } = new();
public static void Initialize()
{
diff --git a/src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs b/src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs
index ce62abdccc..efd703c1aa 100644
--- a/src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs
+++ b/src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs
@@ -7,9 +7,13 @@ internal class TizenThreadingInterface : IPlatformThreadingInterface
internal event Action? TickExecuted;
private bool _signaled;
-#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- internal static SynchronizationContext MainloopContext { get; set; }
-#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ private static SynchronizationContext? s_mainloopContext;
+
+ internal static SynchronizationContext MainloopContext
+ {
+ get => s_mainloopContext ?? throw new InvalidOperationException($"{nameof(MainloopContext)} hasn't been set");
+ set => s_mainloopContext = value;
+ }
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
diff --git a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs
index ae902353f4..3e6e128d8f 100644
--- a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs
+++ b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs
@@ -110,7 +110,7 @@ internal class TopLevelImpl : ITopLevelImpl
internal void TextInput(string text)
{
if (Input == null) return;
- var args = new RawTextInputEventArgs(TizenKeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, _view.InputRoot, text);
+ var args = new RawTextInputEventArgs(TizenKeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _view.InputRoot, text);
Input(args);
}
diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
index 1f20b3ed08..fd21d9b5e1 100644
--- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
@@ -1,8 +1,9 @@
-using System;
+#nullable enable
+
+using System;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Direct2D1.Media;
using Avalonia.Platform;
-using Avalonia.Rendering;
using Avalonia.Win32.Interop;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
diff --git a/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs b/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs
index 98ed64f0a2..51a4e8baf5 100644
--- a/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs
+++ b/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs
@@ -1,17 +1,16 @@
using System.Runtime.InteropServices;
-using Avalonia.Win32.DirectX;
namespace Avalonia.Win32.DComposition;
[StructLayout(LayoutKind.Sequential)]
-public struct DXGI_RATIONAL
+internal struct DXGI_RATIONAL
{
public uint Numerator;
public uint Denominator;
}
[StructLayout(LayoutKind.Sequential)]
-public struct DCOMPOSITION_FRAME_STATISTICS
+internal struct DCOMPOSITION_FRAME_STATISTICS
{
public long lastFrameTime;
public DXGI_RATIONAL currentCompositionRate;
diff --git a/src/Windows/Avalonia.Win32/Win32Com/win32.idl b/src/Windows/Avalonia.Win32/Win32Com/win32.idl
index 689f5b7a2e..196aa983f3 100644
--- a/src/Windows/Avalonia.Win32/Win32Com/win32.idl
+++ b/src/Windows/Avalonia.Win32/Win32Com/win32.idl
@@ -80,9 +80,9 @@ interface IShellItem : IUnknown
HRESULT GetParent([out] IShellItem** ppsi);
- HRESULT GetDisplayName(
+ int GetDisplayName(
[in] uint sigdnName,
- [out, string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName);
+ [string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName);
HRESULT GetAttributes(
[in] ULONG sfgaoMask,
diff --git a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs
index c987732768..c4fe73b985 100644
--- a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs
+++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.ComponentModel;
@@ -15,10 +14,12 @@ namespace Avalonia.Win32
{
internal class Win32StorageProvider : BclStorageProvider
{
- private const uint SIGDN_FILESYSPATH = 0x80058000;
+ private const uint SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000;
- private const FILEOPENDIALOGOPTIONS DefaultDialogOptions = FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE |
- FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE | FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT;
+ private const FILEOPENDIALOGOPTIONS DefaultDialogOptions =
+ FILEOPENDIALOGOPTIONS.FOS_PATHMUSTEXIST | FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM |
+ FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE | FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
+ FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT;
private readonly WindowImpl _windowImpl;
@@ -35,21 +36,23 @@ namespace Avalonia.Win32
public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
- var files = await ShowFilePicker(
+ return await ShowFilePicker(
true, true,
options.AllowMultiple, false,
- options.Title, null, options.SuggestedStartLocation, null, null);
- return files.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray();
+ options.Title, null, options.SuggestedStartLocation, null, null,
+ f => new BclStorageFolder(new DirectoryInfo(f)))
+ .ConfigureAwait(false);
}
public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options)
{
- var files = await ShowFilePicker(
+ return await ShowFilePicker(
true, false,
options.AllowMultiple, false,
options.Title, null, options.SuggestedStartLocation,
- null, options.FileTypeFilter);
- return files.Select(f => new BclStorageFile(new FileInfo(f))).ToArray();
+ null, options.FileTypeFilter,
+ f => new BclStorageFile(new FileInfo(f)))
+ .ConfigureAwait(false);
}
public override async Task SaveFilePickerAsync(FilePickerSaveOptions options)
@@ -58,11 +61,13 @@ namespace Avalonia.Win32
false, false,
false, options.ShowOverwritePrompt,
options.Title, options.SuggestedFileName, options.SuggestedStartLocation,
- options.DefaultExtension, options.FileTypeChoices);
- return files.Select(f => new BclStorageFile(new FileInfo(f))).FirstOrDefault();
+ options.DefaultExtension, options.FileTypeChoices,
+ f => new BclStorageFile(new FileInfo(f)))
+ .ConfigureAwait(false);
+ return files.Count > 0 ? files[0] : null;
}
- private unsafe Task> ShowFilePicker(
+ private unsafe Task> ShowFilePicker(
bool isOpenFile,
bool openFolder,
bool allowMultiple,
@@ -71,11 +76,13 @@ namespace Avalonia.Win32
string? suggestedFileName,
IStorageFolder? folder,
string? defaultExtension,
- IReadOnlyList? filters)
+ IReadOnlyList? filters,
+ Func convert)
+ where TStorageItem : IStorageItem
{
return Task.Run(() =>
{
- IEnumerable result = Array.Empty();
+ IReadOnlyList result = Array.Empty();
try
{
var clsid = isOpenFile ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog;
@@ -101,7 +108,7 @@ namespace Avalonia.Win32
if (defaultExtension is null)
{
- defaultExtension = String.Empty;
+ defaultExtension = string.Empty;
}
fixed (char* pExt = defaultExtension)
@@ -162,22 +169,22 @@ namespace Avalonia.Win32
var shellItemArray = fileOpenDialog.Results;
var count = shellItemArray.Count;
- var results = new List();
+ var results = new List();
for (int i = 0; i < count; i++)
{
var shellItem = shellItemArray.GetItemAt(i);
- if (GetAbsoluteFilePath(shellItem) is { } selected)
+ if (GetParsingName(shellItem) is { } selected)
{
- results.Add(selected);
+ results.Add(convert(selected));
}
}
result = results;
}
else if (frm.Result is { } shellItem
- && GetAbsoluteFilePath(shellItem) is { } singleResult)
+ && GetParsingName(shellItem) is { } singleResult)
{
- result = new[] { singleResult };
+ result = new[] { convert(singleResult) };
}
return result;
@@ -191,18 +198,23 @@ namespace Avalonia.Win32
}
- private static unsafe string? GetAbsoluteFilePath(IShellItem shellItem)
+ private static string? GetParsingName(IShellItem shellItem)
+ {
+ return GetDisplayName(shellItem, SIGDN_DESKTOPABSOLUTEPARSING);
+ }
+
+ private static unsafe string? GetDisplayName(IShellItem shellItem, uint sigdnName)
{
- var pszString = new IntPtr(shellItem.GetDisplayName(SIGDN_FILESYSPATH));
- if (pszString != IntPtr.Zero)
+ char* pszString = null;
+ if (shellItem.GetDisplayName(sigdnName, &pszString) == 0)
{
try
{
- return Marshal.PtrToStringUni(pszString);
+ return Marshal.PtrToStringUni((IntPtr)pszString);
}
finally
{
- Marshal.FreeCoTaskMem(pszString);
+ Marshal.FreeCoTaskMem((IntPtr)pszString);
}
}
return default;
@@ -224,7 +236,7 @@ namespace Avalonia.Win32
for (int i = 0; i < filters.Count; i++)
{
var filter = filters[i];
- if (filter.Patterns is null || !filter.Patterns.Any())
+ if (filter.Patterns is null || filter.Patterns.Count == 0)
{
continue;
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 7a3f411195..0a1a1e898f 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -615,6 +615,12 @@ namespace Avalonia.Win32
{
_lastWindowState = windowState;
+ var newWindowProperties = _windowProperties;
+
+ newWindowProperties.WindowState = windowState;
+
+ UpdateWindowProperties(newWindowProperties);
+
WindowStateChanged?.Invoke(windowState);
if (_isClientAreaExtended)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index d83c72eb1f..8d56c52ed8 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -1087,6 +1087,8 @@ namespace Avalonia.Win32
throw new ArgumentException("Invalid WindowState.");
}
+ newWindowProperties.WindowState = state;
+
UpdateWindowProperties(newWindowProperties);
if (command.HasValue)
@@ -1292,7 +1294,7 @@ namespace Avalonia.Win32
if (IsWindowVisible(_hwnd))
style |= WindowStyles.WS_VISIBLE;
- if (newProperties.IsResizable)
+ if (newProperties.IsResizable || newProperties.WindowState == WindowState.Maximized)
{
style |= WindowStyles.WS_SIZEFRAME;
style |= WindowStyles.WS_MAXIMIZEBOX;
@@ -1303,7 +1305,7 @@ namespace Avalonia.Win32
style &= ~WindowStyles.WS_MAXIMIZEBOX;
}
- const WindowStyles fullDecorationFlags = WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU;
+ const WindowStyles fullDecorationFlags = WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU | WindowStyles.WS_THICKFRAME | WindowStyles.WS_BORDER;
if (newProperties.Decorations == SystemDecorations.Full)
{
@@ -1313,7 +1315,7 @@ namespace Avalonia.Win32
{
style &= ~fullDecorationFlags;
- if (newProperties.Decorations == SystemDecorations.BorderOnly && WindowState != WindowState.Maximized)
+ if (newProperties.Decorations == SystemDecorations.BorderOnly && newProperties.WindowState != WindowState.Maximized)
{
style |= WindowStyles.WS_THICKFRAME | WindowStyles.WS_BORDER;
}
@@ -1487,6 +1489,7 @@ namespace Avalonia.Win32
public bool IsResizable;
public SystemDecorations Decorations;
public bool IsFullScreen;
+ public WindowState WindowState;
}
private struct ResizeReasonScope : IDisposable
diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
index f2da804107..b6bbdcf43a 100644
--- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
+++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
@@ -33,6 +33,7 @@ namespace Avalonia.Win32
try
{
var ret = p.IsReady;
+ _ = p.TotalSize; // try to read size as a proof of read access.
return ret;
}
catch (Exception e)
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 939df1a3a0..a2f206cdd2 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -1,8 +1,13 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
+using System.Runtime.Versioning;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
@@ -10,7 +15,6 @@ using Avalonia.Input.TextInput;
using Avalonia.iOS.Storage;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
-using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using CoreAnimation;
using Foundation;
@@ -23,12 +27,15 @@ namespace Avalonia.iOS
{
public partial class AvaloniaView : UIView, ITextInputMethodImpl
{
- internal IInputRoot InputRoot { get; private set; }
- private TopLevelImpl _topLevelImpl;
- private EmbeddableControlRoot _topLevel;
- private TouchHandler _touches;
- private TextInputMethodClient _client;
- private IAvaloniaViewController _controller;
+ internal IInputRoot InputRoot
+ => _inputRoot ?? throw new InvalidOperationException($"{nameof(IWindowImpl.SetInputRoot)} must have been called");
+
+ private readonly TopLevelImpl _topLevelImpl;
+ private readonly EmbeddableControlRoot _topLevel;
+ private readonly TouchHandler _touches;
+ private TextInputMethodClient? _client;
+ private IAvaloniaViewController? _controller;
+ private IInputRoot? _inputRoot;
public AvaloniaView()
{
@@ -40,6 +47,15 @@ namespace Avalonia.iOS
_topLevel.StartRendering();
+ InitEagl();
+ MultipleTouchEnabled = true;
+ }
+
+ [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")]
+ [SupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("maccatalyst")]
+ private void InitEagl()
+ {
var l = (CAEAGLLayer)Layer;
l.ContentsScale = UIScreen.MainScreen.Scale;
l.Opaque = true;
@@ -48,7 +64,6 @@ namespace Avalonia.iOS
EAGLDrawableProperty.ColorFormat, EAGLColorFormat.RGBA8
);
_topLevelImpl.Surfaces = new[] { new EaglLayerSurface(l) };
- MultipleTouchEnabled = true;
}
///
@@ -58,7 +73,7 @@ namespace Avalonia.iOS
public override bool CanResignFirstResponder => true;
///
- public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
+ public override void TraitCollectionDidChange(UITraitCollection? previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
@@ -89,6 +104,7 @@ namespace Avalonia.iOS
private readonly IStorageProvider _storageProvider;
internal readonly InsetsManager _insetsManager;
private readonly ClipboardImpl _clipboard;
+ private IDisposable? _paddingInsets;
public AvaloniaView View => _view;
@@ -98,9 +114,19 @@ namespace Avalonia.iOS
_nativeControlHost = new NativeControlHostImpl(view);
_storageProvider = new IOSStorageProvider(view);
_insetsManager = new InsetsManager(view);
- _insetsManager.DisplayEdgeToEdgeChanged += (sender, b) =>
+ _insetsManager.DisplayEdgeToEdgeChanged += (_, edgeToEdge) =>
{
- view._topLevel.Padding = b ? default : _insetsManager.SafeAreaPadding;
+ // iOS doesn't add any paddings/margins to the application by itself.
+ // Application is fully responsible for safe area paddings.
+ // So, unlikely to android, we need to "fake" safe area insets when edge to edge is disabled.
+ _paddingInsets?.Dispose();
+ if (!edgeToEdge && view._controller is { } controller)
+ {
+ _paddingInsets = view._topLevel.SetValue(
+ TemplatedControl.PaddingProperty,
+ controller.SafeAreaPadding,
+ BindingPriority.Style); // lower priority, so it can be redefined by user
+ }
};
_clipboard = new ClipboardImpl();
}
@@ -119,19 +145,19 @@ namespace Avalonia.iOS
public void SetInputRoot(IInputRoot inputRoot)
{
- _view.InputRoot = inputRoot;
+ _view._inputRoot = inputRoot;
}
public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y);
- public void SetCursor(ICursorImpl _)
+ public void SetCursor(ICursorImpl? cursor)
{
// no-op
}
- public IPopupImpl CreatePopup()
+ public IPopupImpl? CreatePopup()
{
// In-window popups
return null;
@@ -145,18 +171,16 @@ namespace Avalonia.iOS
public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height);
public Size? FrameSize => null;
public double RenderScaling => _view.ContentScaleFactor;
- public IEnumerable Surfaces { get; set; }
- public Action Input { get; set; }
- public Action Paint { get; set; }
- public Action Resized { get; set; }
- public Action ScalingChanged { get; set; }
- public Action TransparencyLevelChanged { get; set; }
- public Action Closed { get; set; }
-
- public Action LostFocus { get; set; }
-
- // legacy no-op
- public IMouseDevice MouseDevice { get; } = new MouseDevice();
+ public IEnumerable Surfaces { get; set; } = Array.Empty();
+ public Action? Input { get; set; }
+ public Action? Paint { get; set; }
+ public Action? Resized { get; set; }
+ public Action? ScalingChanged { get; set; }
+ public Action? TransparencyLevelChanged { get; set; }
+ public Action? Closed { get; set; }
+
+ public Action? LostFocus { get; set; }
+
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
@@ -213,13 +237,13 @@ namespace Avalonia.iOS
return new Class(typeof(CAEAGLLayer));
}
- public override void TouchesBegan(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
+ public override void TouchesBegan(NSSet touches, UIEvent? evt) => _touches.Handle(touches, evt);
- public override void TouchesMoved(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
+ public override void TouchesMoved(NSSet touches, UIEvent? evt) => _touches.Handle(touches, evt);
- public override void TouchesEnded(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
+ public override void TouchesEnded(NSSet touches, UIEvent? evt) => _touches.Handle(touches, evt);
- public override void TouchesCancelled(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
+ public override void TouchesCancelled(NSSet touches, UIEvent? evt) => _touches.Handle(touches, evt);
public override void LayoutSubviews()
{
@@ -227,9 +251,9 @@ namespace Avalonia.iOS
base.LayoutSubviews();
}
- public Control Content
+ public Control? Content
{
- get => (Control)_topLevel.Content;
+ get => (Control?)_topLevel.Content;
set => _topLevel.Content = value;
}
}
diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs
index 8f7c1f3308..f003c4c6d7 100644
--- a/src/iOS/Avalonia.iOS/EaglDisplay.cs
+++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Runtime.Versioning;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Reactive;
@@ -7,6 +8,8 @@ using OpenGLES;
namespace Avalonia.iOS
{
+ [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")]
+ [SupportedOSPlatform("ios")]
class EaglPlatformGraphics : IPlatformGraphics
{
public IPlatformGraphicsContext GetSharedContext() => Context;
@@ -28,6 +31,8 @@ namespace Avalonia.iOS
}
}
+ [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")]
+ [SupportedOSPlatform("ios")]
class GlContext : IGlContext
{
public EAGLContext Context { get; private set; }
diff --git a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs
index 88812653d7..6323bb3acb 100644
--- a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs
+++ b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs
@@ -1,5 +1,6 @@
using System;
+using System.Runtime.Versioning;
using System.Threading;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Surfaces;
@@ -7,6 +8,8 @@ using CoreAnimation;
namespace Avalonia.iOS
{
+ [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")]
+ [SupportedOSPlatform("ios")]
class EaglLayerSurface : IGlPlatformSurface
{
private readonly CAEAGLLayer _layer;
diff --git a/src/iOS/Avalonia.iOS/InsetsManager.cs b/src/iOS/Avalonia.iOS/InsetsManager.cs
index bd6f989dbd..54b769c567 100644
--- a/src/iOS/Avalonia.iOS/InsetsManager.cs
+++ b/src/iOS/Avalonia.iOS/InsetsManager.cs
@@ -10,7 +10,7 @@ internal class InsetsManager : IInsetsManager
{
private readonly AvaloniaView _view;
private IAvaloniaViewController? _controller;
- private bool _displayEdgeToEdge;
+ private bool _displayEdgeToEdge = true;
public InsetsManager(AvaloniaView view)
{
@@ -30,29 +30,6 @@ internal class InsetsManager : IInsetsManager
}
}
- public SystemBarTheme? SystemBarTheme
- {
- get => _controller?.PreferredStatusBarStyle switch
- {
- UIStatusBarStyle.LightContent => Controls.Platform.SystemBarTheme.Dark,
- UIStatusBarStyle.DarkContent => Controls.Platform.SystemBarTheme.Light,
- _ => null
- };
- set
- {
- if (_controller != null)
- {
- _controller.PreferredStatusBarStyle = value switch
- {
- Controls.Platform.SystemBarTheme.Light => UIStatusBarStyle.DarkContent,
- Controls.Platform.SystemBarTheme.Dark => UIStatusBarStyle.LightContent,
- null => UIStatusBarStyle.Default,
- _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
- };
- }
- }
- }
-
public bool? IsSystemBarVisible
{
get => _controller?.PrefersStatusBarHidden == false;
@@ -76,11 +53,12 @@ internal class InsetsManager : IInsetsManager
{
_displayEdgeToEdge = value;
DisplayEdgeToEdgeChanged?.Invoke(this, value);
+ SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
}
}
}
- public Thickness SafeAreaPadding => _controller?.SafeAreaPadding ?? default;
+ public Thickness SafeAreaPadding => _displayEdgeToEdge ? _controller?.SafeAreaPadding ?? default : default;
public Color? SystemBarColor { get; set; }
}
diff --git a/src/iOS/Avalonia.iOS/LayerFbo.cs b/src/iOS/Avalonia.iOS/LayerFbo.cs
index c19fb2ee8d..d971858b6d 100644
--- a/src/iOS/Avalonia.iOS/LayerFbo.cs
+++ b/src/iOS/Avalonia.iOS/LayerFbo.cs
@@ -1,10 +1,13 @@
using System;
+using System.Runtime.Versioning;
using Avalonia.OpenGL;
using CoreAnimation;
using OpenGLES;
namespace Avalonia.iOS
{
+ [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")]
+ [SupportedOSPlatform("ios")]
internal class LayerFbo
{
private readonly EAGLContext _context;
@@ -90,6 +93,8 @@ namespace Avalonia.iOS
}
}
+ [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")]
+ [SupportedOSPlatform("ios")]
class SizeSynchronizedLayerFbo : IDisposable
{
private readonly EAGLContext _context;
diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs
index 34018560e2..bb61861596 100644
--- a/src/iOS/Avalonia.iOS/Platform.cs
+++ b/src/iOS/Avalonia.iOS/Platform.cs
@@ -38,7 +38,7 @@ namespace Avalonia.iOS
var keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(GlFeature)
+ .Bind().ToConstant((IPlatformGraphics) GlFeature)
.Bind().ToConstant(new CursorFactoryStub())
.Bind().ToConstant(new WindowingPlatformStub())
.Bind().ToSingleton()
diff --git a/src/iOS/Avalonia.iOS/Properties/AssemblyInfo.cs b/src/iOS/Avalonia.iOS/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..23e87222e6
--- /dev/null
+++ b/src/iOS/Avalonia.iOS/Properties/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using System.Runtime.Versioning;
+
+// Remove spurious warnings for maccatalyst (which isn't targeted): https://github.com/dotnet/roslyn-analyzers/issues/6269
+[assembly: UnsupportedOSPlatform("maccatalyst")]
diff --git a/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs b/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs
index 784937cb10..8349258c98 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs
@@ -17,7 +17,7 @@ internal sealed class IOSSecurityScopedStream : Stream
internal IOSSecurityScopedStream(NSUrl url, FileAccess access)
{
_document = new UIDocument(url);
- var path = _document.FileUrl.Path;
+ var path = _document.FileUrl.Path!;
_url = url;
_url.StartAccessingSecurityScopedResource();
_stream = File.Open(path, FileMode.Open, access);
diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
index 7ab593e121..09fd6e88ec 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
@@ -47,7 +47,12 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
Logger.TryGet(LogEventLevel.Error, LogArea.IOSPlatform)?.
Log(this, "GetBasicPropertiesAsync returned an error: {ErrorCode} {ErrorMessage}", error.Code, error.LocalizedFailureReason);
}
- return Task.FromResult(new StorageItemProperties(attributes?.Size, (DateTime)attributes?.CreationDate, (DateTime)attributes?.ModificationDate));
+
+ var properties = attributes is null ?
+ new StorageItemProperties() :
+ new StorageItemProperties(attributes.Size, (DateTime)attributes.CreationDate, (DateTime)attributes.ModificationDate);
+
+ return Task.FromResult(properties);
}
public Task GetParentAsync()
@@ -62,7 +67,7 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
: Task.FromException(new NSErrorException(error));
}
- public async Task MoveAsync(IStorageFolder destination)
+ public Task MoveAsync(IStorageFolder destination)
{
if (destination is not IOSStorageFolder folder)
{
@@ -75,8 +80,8 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
if (NSFileManager.DefaultManager.Move(folder.Url, newPath, out var error))
{
return isDir
- ? new IOSStorageFolder(newPath)
- : new IOSStorageFile(newPath);
+ ? Task.FromResult(new IOSStorageFolder(newPath))
+ : Task.FromResult(new IOSStorageFile(newPath));
}
if (error is not null)
@@ -84,7 +89,7 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
throw new NSErrorException(error);
}
- return null;
+ return Task.FromResult(null);
}
public Task ReleaseBookmarkAsync()
diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs
index 935cf227da..a5a0c0759f 100644
--- a/src/iOS/Avalonia.iOS/TextInputResponder.cs
+++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs
@@ -159,17 +159,20 @@ partial class AvaloniaView
{
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "Triggering key press {key}", key);
- _view._topLevelImpl.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
- RawKeyEventType.KeyDown, key, RawInputModifiers.None, physicalKey, keySymbol));
+ if (_view._topLevelImpl.Input is { } input)
+ {
+ input.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
+ RawKeyEventType.KeyDown, key, RawInputModifiers.None, physicalKey, keySymbol));
- _view._topLevelImpl.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
- RawKeyEventType.KeyUp, key, RawInputModifiers.None, physicalKey, keySymbol));
+ input.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
+ RawKeyEventType.KeyUp, key, RawInputModifiers.None, physicalKey, keySymbol));
+ }
}
private void TextInput(string text)
{
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "Triggering text input {text}", text);
- _view._topLevelImpl.Input(new RawTextInputEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot, text));
+ _view._topLevelImpl.Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot, text));
}
void IUIKeyInput.InsertText(string text)
@@ -182,6 +185,10 @@ partial class AvaloniaView
switch (ReturnKeyType)
{
+ case UIReturnKeyType.Next:
+ FocusManager.GetFocusManager(_view._topLevel)?
+ .TryMoveFocus(NavigationDirection.Next);
+ break;
case UIReturnKeyType.Done:
case UIReturnKeyType.Go:
case UIReturnKeyType.Send:
diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
index c8608451e5..ead954d641 100644
--- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
+++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs b/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
index 71f34d173c..9bf82ae5bd 100644
--- a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
+++ b/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using XamlX;
using XamlX.Compiler;
using XamlX.Emit;
using XamlX.Transform;
@@ -13,16 +15,20 @@ internal sealed class MiniCompiler : XamlCompiler
{
public const string AvaloniaXmlnsDefinitionAttribute = "Avalonia.Metadata.XmlnsDefinitionAttribute";
+ [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = TrimmingMessages.Roslyn)]
public static MiniCompiler CreateDefault(RoslynTypeSystem typeSystem, params string[] additionalTypes)
{
var mappings = new XamlLanguageTypeMappings(typeSystem);
foreach (var additionalType in additionalTypes)
mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));
+ var diagnosticsHandler = new XamlDiagnosticsHandler();
+
var configuration = new TransformerConfiguration(
typeSystem,
typeSystem.Assemblies.First(),
- mappings);
+ mappings,
+ diagnosticsHandler: diagnosticsHandler);
return new MiniCompiler(configuration);
}
@@ -47,4 +53,4 @@ internal sealed class MiniCompiler : XamlCompiler
XamlRuntimeContext context,
bool needContextLocal) =>
throw new NotSupportedException();
-}
\ No newline at end of file
+}
diff --git a/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs b/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs
index 851bc3f29b..495d960ab5 100644
--- a/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs
+++ b/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -31,11 +32,13 @@ internal class RoslynTypeSystem : IXamlTypeSystem
Assemblies
.FirstOrDefault(a => string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase));
+ [UnconditionalSuppressMessage("Trimming", "IL2092", Justification = TrimmingMessages.Roslyn)]
public IXamlType FindType(string name) =>
_assemblies
.Select(assembly => assembly.FindType(name))
.FirstOrDefault(type => type != null);
+ [UnconditionalSuppressMessage("Trimming", "IL2092", Justification = TrimmingMessages.Roslyn)]
public IXamlType FindType(string name, string assembly) =>
_assemblies
.Select(assemblyInstance => assemblyInstance.FindType(name))
diff --git a/src/tools/Avalonia.Generators/TrimmingMessages.cs b/src/tools/Avalonia.Generators/TrimmingMessages.cs
new file mode 100644
index 0000000000..57e005a649
--- /dev/null
+++ b/src/tools/Avalonia.Generators/TrimmingMessages.cs
@@ -0,0 +1,6 @@
+namespace Avalonia.Generators;
+
+internal static class TrimmingMessages
+{
+ public const string Roslyn = "Roslyn is not getting trimmed.";
+}
diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.ConfigHelpers.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.ConfigHelpers.cs
index 5d59af6951..faad2458fb 100644
--- a/src/tools/DevGenerators/CompositionGenerator/Generator.ConfigHelpers.cs
+++ b/src/tools/DevGenerators/CompositionGenerator/Generator.ConfigHelpers.cs
@@ -1,23 +1,22 @@
-
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-using static Avalonia.SourceGenerator.CompositionGenerator.Extensions;
+
namespace Avalonia.SourceGenerator.CompositionGenerator;
public partial class Generator
{
- class GeneratorTypeInfo
+ private sealed class GeneratorTypeInfo
{
- public TypeSyntax RoslynType { get; set; }
- public string FilteredTypeName { get; set; }
+ public TypeSyntax RoslynType { get; set; } = null!;
+ public string FilteredTypeName { get; set; } = null!;
public bool IsObject { get; set; }
public bool IsPassthrough { get; set; }
- public string ServerType { get; set; }
+ public string ServerType { get; set; } = null!;
public bool IsNullable { get; set; }
}
- private Dictionary _typeInfoCache = new();
+ private readonly Dictionary _typeInfoCache = new();
private GeneratorTypeInfo GetTypeInfo(string type)
{
@@ -53,4 +52,4 @@ public partial class Generator
IsNullable = isNullable
};
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs
index 7b401918ce..6b7ed7f257 100644
--- a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs
+++ b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs
@@ -14,8 +14,8 @@ public class DispatcherTests
{
class SimpleDispatcherImpl : IDispatcherImpl, IDispatcherImplWithPendingInput
{
- private Thread _loopThread = Thread.CurrentThread;
- private object _lock = new();
+ private readonly Thread _loopThread = Thread.CurrentThread;
+ private readonly object _lock = new();
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _loopThread;
public void Signal()
{
@@ -221,7 +221,8 @@ public class DispatcherTests
0 => 1,
1 => 4,
2 => 8,
- 3 => 10
+ 3 => 10,
+ _ => throw new InvalidOperationException($"Unexpected value {c}")
};
Assert.Equal(Enumerable.Range(0, expectedCount), actions);
@@ -380,7 +381,7 @@ public class DispatcherTests
class WaitHelper : SynchronizationContext, NonPumpingLockHelper.IHelperImpl
{
public int WaitCount;
- public int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+ public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
WaitCount++;
return base.Wait(waitHandles, waitAll, millisecondsTimeout);
@@ -502,4 +503,4 @@ public class DispatcherTests
t.GetAwaiter().GetResult();
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
index aa2e47de2d..0bc3114665 100644
--- a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
@@ -583,5 +583,129 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Null(root.FocusManager.GetFocusedElement());
}
}
+
+ [Fact]
+ public void Removing_Focused_Element_Inside_Focus_Scope_Activates_Root_Focus_Scope()
+ {
+ // Issue #13325
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ Button innerButton, intermediateButton, outerButton;
+ TestFocusScope innerScope;
+ var root = new TestRoot
+ {
+ Child = new StackPanel
+ {
+ Children =
+ {
+ // Intermediate focus scope to make sure that the root focus scope gets
+ // activated, not this one.
+ new TestFocusScope
+ {
+ Children =
+ {
+ (innerScope = new TestFocusScope
+ {
+ Children =
+ {
+ (innerButton = new Button()),
+ }
+ }),
+ (intermediateButton = new Button()),
+ }
+ },
+ (outerButton = new Button()),
+ }
+ }
+ };
+
+ // Focus a control in each scope, ending with the innermost one.
+ outerButton.Focus();
+ intermediateButton.Focus();
+ innerButton.Focus();
+
+ // Remove the focused control from the tree.
+ ((Panel)innerButton.Parent).Children.Remove(innerButton);
+
+ var focusManager = Assert.IsType(root.FocusManager);
+ Assert.Same(outerButton, focusManager.GetFocusedElement());
+ Assert.Null(focusManager.GetFocusedElement(innerScope));
+ }
+
+ [Fact]
+ public void Removing_Focus_Scope_Activates_Root_Focus_Scope()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ Button innerButton, outerButton;
+ TestFocusScope innerScope;
+ var root = new TestRoot
+ {
+ Child = new StackPanel
+ {
+ Children =
+ {
+ (innerScope = new TestFocusScope
+ {
+ Children =
+ {
+ (innerButton = new Button()),
+ }
+ }),
+ (outerButton = new Button()),
+ }
+ }
+ };
+
+ // Focus a control in the top-level and inner focus scopes.
+ outerButton.Focus();
+ innerButton.Focus();
+
+ // Remove the inner focus scope.
+ ((Panel)innerScope.Parent).Children.Remove(innerScope);
+
+ var focusManager = Assert.IsType(root.FocusManager);
+ Assert.Same(outerButton, focusManager.GetFocusedElement());
+ }
+
+ [Fact]
+ public void Switching_Focus_Scope_Changes_Focus()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ Button innerButton, outerButton;
+ TestFocusScope innerScope;
+ var root = new TestRoot
+ {
+ Child = new StackPanel
+ {
+ Children =
+ {
+ (innerScope = new TestFocusScope
+ {
+ Children =
+ {
+ (innerButton = new Button()),
+ }
+ }),
+ (outerButton = new Button()),
+ }
+ }
+ };
+
+ // Focus a control in the top-level and inner focus scopes.
+ outerButton.Focus();
+ innerButton.Focus();
+
+ var focusManager = Assert.IsType(root.FocusManager);
+ Assert.Same(innerButton, focusManager.GetFocusedElement());
+
+ focusManager.SetFocusScope(root);
+ Assert.Same(outerButton, focusManager.GetFocusedElement());
+
+ focusManager.SetFocusScope(innerScope);
+ Assert.Same(innerButton, focusManager.GetFocusedElement());
+ }
+
+ private class TestFocusScope : Panel, IFocusScope
+ {
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs
index 28cb9c9282..c486a66da0 100644
--- a/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Windows.Input;
-using Avalonia.Controls;
+using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.UnitTests;
@@ -112,7 +110,7 @@ namespace Avalonia.Base.UnitTests.Input
button.KeyBindings.Add(new KeyBinding
{
Gesture = new KeyGesture(Key.O, KeyModifiers.Control),
- Command = new DelegateCommand(() =>
+ Command = new Utilities.DelegateCommand(() =>
{
button.KeyBindings.Clear();
++raised;
@@ -134,15 +132,6 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(1, raised);
}
- private class DelegateCommand : ICommand
- {
- private readonly Action _action;
- public DelegateCommand(Action action) => _action = action;
- public event EventHandler CanExecuteChanged { add { } remove { } }
- public bool CanExecute(object parameter) => true;
- public void Execute(object parameter) => _action();
- }
-
[Fact]
public void Control_Focus_Should_Be_Set_Before_FocusedElement_Raises_PropertyChanged()
{
diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
index 0b3d1a275b..5d8ffd1e13 100644
--- a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
@@ -1273,5 +1274,42 @@ namespace Avalonia.Base.UnitTests.Input
Assert.True(button.IsFocused);
}
+
+ [Fact]
+ public void Next_Skip_Button_When_Command_CanExecute_Is_False()
+ {
+ Button current;
+ Button expected;
+ bool executed = false;
+
+ var top = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ new StackPanel
+ {
+ Children =
+ {
+ (current = new Button { Name = "Button1" }),
+ new Button
+ {
+ Name = "Button2",
+ Command = new Utilities.DelegateCommand(()=>executed = true,
+ _ => false),
+ },
+ (expected = new Button { Name = "Button3" }),
+ }
+ }
+ }
+ };
+
+ var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next) as Button;
+
+ Assert.Equal(expected.Name, result?.Name);
+ Assert.False(executed);
+ }
+
+
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Media/EffectTests.cs b/tests/Avalonia.Base.UnitTests/Media/EffectTests.cs
index f374018438..73023c1b51 100644
--- a/tests/Avalonia.Base.UnitTests/Media/EffectTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/EffectTests.cs
@@ -25,9 +25,8 @@ public class EffectTests
InlineData("drop-shadow ( 10 20 30 #ffff00ff ) ", 10, 20, 30, 0xffff00ff),
InlineData("drop-shadow(10 20 30 red)", 10, 20, 30, 0xffff0000),
InlineData("drop-shadow ( 10 20 30 red ) ", 10, 20, 30, 0xffff0000),
- InlineData("drop-shadow(10 20 30 rgba(100, 30, 45, 90%))", 10, 20, 30, 0x90641e2d),
- InlineData("drop-shadow(10 20 30 rgba(100, 30, 45, 90%) ) ", 10, 20, 30, 0x90641e2d),
-
+ InlineData("drop-shadow(10 20 30 rgba(100, 30, 45, 90%))", 10, 20, 30, 0xe6641e2d),
+ InlineData("drop-shadow(10 20 30 rgba(100, 30, 45, 90%) ) ", 10, 20, 30, 0xe6641e2d)
]
public void Parse_Parses_DropShadow(string s, double x, double y, double r, uint color)
{
@@ -36,6 +35,7 @@ public class EffectTests
Assert.Equal(y, effect.OffsetY);
Assert.Equal(r, effect.BlurRadius);
Assert.Equal(1, effect.Opacity);
+ Assert.Equal(color, effect.Color.ToUInt32());
}
[Theory,
@@ -44,7 +44,7 @@ public class EffectTests
InlineData("blur()"),
InlineData("blur(123"),
InlineData("blur(aaab)"),
- InlineData("drop-shadow(-10 -20 -30)"),
+ InlineData("drop-shadow(-10 -20 -30)")
]
public void Invalid_Effect_Parse_Fails(string b)
{
@@ -58,10 +58,7 @@ public class EffectTests
InlineData("drop-shadow(10 15 5)", 0, 0, 16, 21),
InlineData("drop-shadow(0 0 5)", 6, 6, 6, 6),
InlineData("drop-shadow(3 3 5)", 3, 3, 9, 9)
-
-
]
-
public static void PaddingIsCorrectlyCalculated(string effect, double left, double top, double right, double bottom)
{
var padding = Effect.Parse(effect).GetEffectOutputPadding();
@@ -70,4 +67,4 @@ public class EffectTests
Assert.Equal(right, padding.Right);
Assert.Equal(bottom, padding.Bottom);
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
index 7f52418790..0551fa5bc4 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
@@ -520,12 +520,12 @@ namespace Avalonia.Base.UnitTests.Styling
private class TestConverter : IValueConverter
{
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- return value.ToString() + "bar";
+ return value + "bar";
}
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
diff --git a/tests/Avalonia.Base.UnitTests/Utilities/DelegateCommand.cs b/tests/Avalonia.Base.UnitTests/Utilities/DelegateCommand.cs
new file mode 100644
index 0000000000..0f9c3a0545
--- /dev/null
+++ b/tests/Avalonia.Base.UnitTests/Utilities/DelegateCommand.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Windows.Input;
+
+namespace Avalonia.Base.UnitTests.Utilities;
+
+internal class DelegateCommand : ICommand
+{
+ private readonly Action _action;
+ private readonly Func _canExecute;
+ public DelegateCommand(Action action, Func canExecute = default)
+ {
+ _action = action;
+ _canExecute = canExecute ?? new(_ => true);
+ }
+
+ public event EventHandler CanExecuteChanged { add { } remove { } }
+ public bool CanExecute(object parameter) => _canExecute(parameter);
+ public void Execute(object parameter) => _action();
+}
diff --git a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs
index b128e6d83a..c6aaa2205c 100644
--- a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs
@@ -1,11 +1,7 @@
-using System;
-using System.Linq;
+using System.Linq;
using Avalonia.Automation.Peers;
-using Avalonia.Automation.Provider;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
-using Avalonia.Platform;
-using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
@@ -41,7 +37,7 @@ namespace Avalonia.Controls.UnitTests.Automation
{
var contentControl = new ContentControl
{
- Template = new FuncControlTemplate((o, ns) =>
+ Template = new FuncControlTemplate((o, _) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
@@ -180,50 +176,5 @@ namespace Avalonia.Controls.UnitTests.Automation
{
return ControlAutomationPeer.CreatePeerForElement(control);
}
-
- private class TestControl : Control
- {
- protected override AutomationPeer OnCreateAutomationPeer()
- {
- return new TestAutomationPeer(this);
- }
- }
-
- private class AutomationTestRoot : TestRoot
- {
- protected override AutomationPeer OnCreateAutomationPeer()
- {
- return new TestRootAutomationPeer(this);
- }
- }
-
- private class TestAutomationPeer : ControlAutomationPeer
- {
- public TestAutomationPeer( Control owner)
- : base(owner)
- {
- }
- }
-
- private class TestRootAutomationPeer : ControlAutomationPeer, IRootProvider
- {
- public TestRootAutomationPeer(Control owner)
- : base(owner)
- {
- }
-
- public ITopLevelImpl PlatformImpl => throw new System.NotImplementedException();
- public event EventHandler? FocusChanged;
-
- public AutomationPeer GetFocus()
- {
- throw new System.NotImplementedException();
- }
-
- public AutomationPeer GetPeerFromPoint(Point p)
- {
- throw new System.NotImplementedException();
- }
- }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
index 9523d5db7f..3de13ba71b 100644
--- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
@@ -430,7 +430,8 @@ namespace Avalonia.Controls.UnitTests
{
new ComboBoxItem()
{
- Content = parentContent.Child
+ Content = parentContent.Child,
+ Template = null // ugly hack, so we can "attach" same child to the two different trees
}
},
Template = GetTemplate()
diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
index 02b6592097..507227eff8 100644
--- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
@@ -363,8 +363,6 @@ namespace Avalonia.Controls.UnitTests
root.Child = null;
- Assert.Null(target.Template);
-
target.Content = null;
Assert.Empty(target.LogicalChildren);
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index 0c24a0d36e..f300d40791 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -594,6 +594,55 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Closing_Should_Restore_Focus()
+ {
+ using (Application())
+ {
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
+ popupImpl.Setup(x => x.Hide()).Verifiable();
+
+ var item = new MenuItem();
+ var sut = new ContextMenu
+ {
+ Items = { item }
+ };
+
+ var button = new Button();
+ var target = new Panel
+ {
+ Children =
+ {
+ button,
+ },
+ ContextMenu = sut
+ };
+
+ var window = PreparedWindow(target);
+ var focusManager = Assert.IsType(window.FocusManager);
+
+ // Show the window and focus the button.
+ window.Show();
+ button.Focus();
+ Assert.Same(button, focusManager.GetFocusedElement());
+
+ // Click to show the context menu.
+ _mouse.Click(target, MouseButton.Right);
+ Assert.True(sut.IsOpen);
+
+ // Hover over the context menu item: this should focus it.
+ _mouse.Enter(item);
+ Assert.Same(item, focusManager.GetFocusedElement());
+
+ // Click the menu item to close the menu.
+ _mouse.Click(item);
+ Assert.False(sut.IsOpen);
+
+ // Focus should be restored to the button.
+ Assert.Same(button, focusManager.GetFocusedElement());
+ }
+ }
+
private static Window PreparedWindow(object content = null)
{
@@ -623,6 +672,8 @@ namespace Avalonia.Controls.UnitTests
windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
var services = TestServices.StyledWindow.With(
+ focusManager: new FocusManager(),
+ keyboardDevice: () => new KeyboardDevice(),
inputManager: new InputManager(),
windowImpl: windowImpl.Object,
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, x => popupImpl.Object));
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index 8302f1afc3..514add01ec 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -717,7 +717,7 @@ namespace Avalonia.Controls.UnitTests
var target = CreateTarget(
dataContext: "Base",
itemsSource: items,
- dataTemplates: new[] { dataTemplate });
+ itemTemplate: dataTemplate);
var panel = Assert.IsAssignableFrom(target.ItemsPanelRoot);
var dataContexts = panel.Children
.Do(x => (x as ContentPresenter)?.UpdateChild())
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index a6a2a02479..56f20c6e8e 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -181,10 +181,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
DataContext = "Base",
- DataTemplates =
- {
- new FuncDataTemplate- ((x, _) => new Button { Content = x })
- },
+ ItemTemplate = new FuncDataTemplate
- ((x, _) => new Button { Content = x }),
ItemsSource = items,
};
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
index 21e25a6f1c..3a74ee6a7f 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Shift_Selecting_From_No_Selection_Selects_From_Start()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -46,7 +46,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Ctrl_Selecting_Raises_SelectionChanged_Events()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -104,7 +104,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -138,7 +138,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -169,7 +169,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -197,7 +197,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -225,7 +225,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -253,7 +253,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Shift_Selecting_Raises_SelectionChanged_Events()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -341,7 +341,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -371,7 +371,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -399,7 +399,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -429,7 +429,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Shift_Right_Click_Should_Not_Select_Multiple()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -456,7 +456,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Ctrl_Right_Click_Should_Not_Select_Multiple()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index c1fd79fccc..5a0c60dff6 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -673,9 +673,10 @@ namespace Avalonia.Controls.UnitTests.Primitives
PlacementTarget = window,
Child = tb
};
- ((ISetLogicalParent)p).SetParent(p.PlacementTarget);
- window.Show();
+ window.Content = p;
+ window.Show();
+ window.Focus();
p.Open();
if (p.Host is OverlayPopupHost host)
@@ -692,7 +693,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var focusManager = window.FocusManager;
var focus = focusManager.GetFocusedElement();
- Assert.True(focus == window);
+ Assert.Same(window, focus);
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 5fba91fafb..f707aea445 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -175,6 +175,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Setting_SelectedIndex_During_Initialize_Should_Select_Item_When_AlwaysSelected_Is_Used()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected
@@ -1227,7 +1229,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -1249,7 +1251,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " });
@@ -1348,7 +1350,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -1366,7 +1368,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present()
{
- using (UnitTestApplication.Start())
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new ListBox
{
@@ -1384,6 +1386,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Adding_Item_Before_SelectedItem_Should_Update_SelectedIndex()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1409,6 +1413,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Removing_Item_Before_SelectedItem_Should_Update_SelectedIndex()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1434,6 +1440,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Binding_SelectedIndex_Selects_Correct_Item()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
// Issue #4496 (part 2)
var items = new ObservableCollection();
@@ -1463,6 +1471,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Binding_SelectedItem_Selects_Correct_Item()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
// Issue #4496 (part 2)
var items = new ObservableCollection();
@@ -1493,6 +1503,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Replacing_Selected_Item_Should_Update_SelectedItem()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1518,6 +1530,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void AutoScrollToSelectedItem_Causes_Scroll_To_SelectedItem()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1543,6 +1557,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void AutoScrollToSelectedItem_Causes_Scroll_To_Initial_SelectedItem()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1607,6 +1623,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void AutoScrollToSelectedItem_Scrolls_When_Reattached_To_Visual_Tree_If_Selection_Changed_While_Detached_From_Visual_Tree()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1639,6 +1657,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void AutoScrollToSelectedItem_Doesnt_Scroll_If_Reattached_To_Visual_Tree_With_No_Selection_Change()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1670,6 +1690,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void AutoScrollToSelectedItem_Causes_Scroll_When_Turned_On()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
var items = new ObservableCollection
{
"Foo",
@@ -1700,6 +1722,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Can_Set_Both_SelectedItem_And_SelectedItems_During_Initialization()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
// Issue #2969.
var target = new ListBox();
var selectedItems = new List();
@@ -1891,6 +1915,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Does_The_Best_It_Can_With_AutoSelecting_ViewModel()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
// Tests the following scenario:
//
// - Items changes from empty to having 1 item
@@ -1932,6 +1958,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Preserves_Initial_SelectedItems_When_Bound()
{
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
// Issue #4272 (there are two issues there, this addresses the second one).
var vm = new SelectionViewModel
{
@@ -2196,6 +2224,56 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(0, selectedItemChangedRaised);
}
+
+ [Fact]
+ public void Should_First_Raise_Property_Changed_Notification_Then_Fire_Selection_Changed_Event()
+ {
+ using var _ = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
+ // Issue #11006
+ var items = new ObservableCollection();
+
+ var vm = new SelectionViewModel
+ {
+ SelectedItem = "" ,
+ };
+
+ var theListBox = new ListBox
+ {
+ DataContext = vm,
+ Template = Template(),
+ ItemsSource = items,
+ SelectionMode = SelectionMode.AlwaysSelected,
+ [!ListBox.SelectedItemProperty] = new Binding("SelectedItem"),
+ };
+
+ var target = new TextBox
+ {
+ Text = "",
+ };
+
+ Prepare(theListBox);
+
+ items.Add("Default");
+ items.Add("First");
+ items.Add("Second");
+ items.Add("Third");
+
+ theListBox.SelectionChanged += (s, e) =>
+ {
+ target.Text = (string)vm.SelectedItem;
+ };
+
+ theListBox.SelectedIndex = 1;
+ Assert.Equal("First", target.Text);
+
+ theListBox.SelectedIndex = 2;
+ Assert.Equal("Second", target.Text);
+
+ theListBox.SelectedIndex = 3;
+ Assert.Equal("Third", target.Text);
+
+ }
private static IDisposable Start()
{
@@ -2346,7 +2424,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
set => base.SelectionMode = value;
}
- public new bool MoveSelection(NavigationDirection direction, bool wrap)
+ public bool MoveSelection(NavigationDirection direction, bool wrap)
{
return base.MoveSelection(direction, wrap);
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
index 9acd42aba6..dbb3e0691d 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
@@ -1,8 +1,9 @@
using Avalonia.Data;
using Avalonia.UnitTests;
-
using Xunit;
+#pragma warning disable CS0618 // Type or member is obsolete -- we're testing these members
+
namespace Avalonia.Controls.Primitives.UnitTests
{
public class ToggleButtonTests
diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
index a7628cf243..31f095ba3a 100644
--- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
@@ -358,6 +358,46 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(100, thumb.Bounds.Top);
}
+ [Fact]
+ public void Deferred_Scrolling_Defers_Scrolling_Until_Pointer_Up()
+ {
+ var content = new TestContent();
+ var target = new ScrollViewer
+ {
+ Template = new FuncControlTemplate(CreateTemplate),
+ IsDeferredScrollingEnabled = true,
+ Content = content,
+ };
+ var root = new TestRoot(target);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ // We're working in absolute coordinates (i.e. relative to the root) and clicking on
+ // the center of the vertical thumb.
+ var thumb = GetVerticalThumb(target);
+ var p = GetRootPoint(thumb, thumb.Bounds.Center);
+
+ Assert.Equal(Vector.Zero, target.Offset);
+ Assert.Equal(0, thumb.Bounds.Top);
+
+ // Press the mouse button in the center of the thumb.
+ _mouse.Down(thumb, position: p);
+ root.LayoutManager.ExecuteLayoutPass();
+
+ // Drag the thumb down 100 pixels.
+ _mouse.Move(thumb, p += new Vector(0, 100));
+ root.LayoutManager.ExecuteLayoutPass();
+
+ Assert.Equal(Vector.Zero, target.Offset); // no change to scroll...
+ Assert.Equal(100, thumb.Bounds.Top); // ...but the Thumb has moved
+
+ // Release the mouse
+ _mouse.Up(thumb, position: p);
+
+ Assert.Equal(new Vector(0, 200), target.Offset);
+ Assert.Equal(100, thumb.Bounds.Top);
+ }
+
[Fact]
public void BringIntoViewOnFocusChange_Scrolls_Child_Control_Into_View_When_Focused()
{
@@ -493,6 +533,7 @@ namespace Avalonia.Controls.UnitTests
[!!Track.ValueProperty] = scrollBar[!!RangeBase.ValueProperty],
[!Track.ViewportSizeProperty] = scrollBar[!ScrollBar.ViewportSizeProperty],
[!Track.OrientationProperty] = scrollBar[!ScrollBar.OrientationProperty],
+ [!Track.DeferThumbDragProperty] = scrollBar.TemplatedParent[!ScrollViewer.IsDeferredScrollingEnabledProperty],
Thumb = new Thumb
{
Template = new FuncControlTemplate(CreateThumbTemplate),
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 7721018453..0a409f1ef5 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
-using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
@@ -547,6 +546,32 @@ namespace Avalonia.Controls.UnitTests
Assert.Same(item, root.FocusManager.GetFocusedElement());
}
+ [Fact]
+ public void TabItem_Header_Should_Be_Settable_By_Style_When_DataContext_Is_Set()
+ {
+ var tabItem = new TabItem
+ {
+ DataContext = "Some DataContext"
+ };
+
+ _ = new TestRoot
+ {
+ Styles =
+ {
+ new Style(x => x.OfType())
+ {
+ Setters =
+ {
+ new Setter(HeaderedContentControl.HeaderProperty, "Header from style")
+ }
+ }
+ },
+ Child = tabItem
+ };
+
+ Assert.Equal("Header from style", tabItem.Header);
+ }
+
private static IControlTemplate TabControlTemplate()
{
return new FuncControlTemplate((parent, scope) =>
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
index 9a9c4d352e..295fc192d7 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
@@ -64,6 +64,30 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Setter_Exceptions_Should_Be_Converter_If_Error_Converter_Set()
+ {
+ using (UnitTestApplication.Start(Services))
+ {
+ var target = new TextBox
+ {
+ DataContext = new ExceptionTest(),
+ [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay),
+ Template = CreateTemplate()
+ };
+ DataValidationErrors.SetErrorConverter(target, err => "Error: " + err);
+
+ target.ApplyTemplate();
+
+ target.Text = "20";
+
+ IEnumerable errors = DataValidationErrors.GetErrors(target);
+ Assert.Single(errors);
+ var error = Assert.IsType(errors.Single());
+ Assert.StartsWith("Error: ", error);
+ }
+ }
+
[Fact]
public void Setter_Exceptions_Should_Set_DataValidationErrors_HasErrors()
{
diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
index 1449f3f759..793c2c6b86 100644
--- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
@@ -98,6 +98,40 @@ namespace Avalonia.Controls.UnitTests
Assert.True(minuteText.Text == "minute");
}
}
+
+ [Fact]
+ [UseEmptyDesignatorCulture]
+ public void Using_12HourClock_On_Culture_With_Empty_Period_Should_Show_Period()
+ {
+ using (UnitTestApplication.Start(Services))
+ {
+ TimePicker timePicker = new TimePicker()
+ {
+ Template = CreateTemplate(), ClockIdentifier = "12HourClock",
+ };
+ timePicker.ApplyTemplate();
+
+ var desc = timePicker.GetVisualDescendants();
+ Assert.True(desc.Count() > 1); //Should be layoutroot grid & button
+
+ Assert.True(desc.ElementAt(1) is Button);
+
+ var container = (desc.ElementAt(1) as Button).Content as Grid;
+ Assert.True(container != null);
+
+ var periodTextHost = container.Children[4] as Border;
+ Assert.NotNull(periodTextHost);
+ var periodText = periodTextHost.Child as TextBlock;
+ Assert.NotNull(periodTextHost);
+
+ TimeSpan ts = TimeSpan.FromHours(10);
+ timePicker.SelectedTime = ts;
+ Assert.False(string.IsNullOrEmpty(periodText.Text));
+
+ timePicker.SelectedTime = null;
+ Assert.False(string.IsNullOrEmpty(periodText.Text));
+ }
+ }
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(),
diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
index 74e506105d..98a131752a 100644
--- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
@@ -293,6 +293,113 @@ namespace Avalonia.Controls.UnitTests
Assert.False(ToolTip.GetIsOpen(target));
}
}
+
+ [Fact]
+ public void Should_Not_Close_When_Pointer_Is_Moved_Over_ToolTip()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+
+ var target = new Decorator()
+ {
+ [ToolTip.TipProperty] = "Tip",
+ [ToolTip.ShowDelayProperty] = 0
+ };
+
+ window.Content = target;
+
+ window.ApplyStyling();
+ window.ApplyTemplate();
+ window.Presenter.ApplyTemplate();
+
+ _mouseHelper.Enter(target);
+ Assert.True(ToolTip.GetIsOpen(target));
+
+ var tooltip = Assert.IsType(target.GetValue(ToolTip.ToolTipProperty));
+ _mouseHelper.Enter(tooltip);
+ _mouseHelper.Leave(target);
+
+ Assert.True(ToolTip.GetIsOpen(target));
+ }
+ }
+
+ [Fact]
+ public void Should_Not_Close_When_Pointer_Is_Moved_From_ToolTip_To_Original_Control()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+
+ var target = new Decorator()
+ {
+ [ToolTip.TipProperty] = "Tip",
+ [ToolTip.ShowDelayProperty] = 0
+ };
+
+ window.Content = target;
+
+ window.ApplyStyling();
+ window.ApplyTemplate();
+ window.Presenter.ApplyTemplate();
+
+ _mouseHelper.Enter(target);
+ Assert.True(ToolTip.GetIsOpen(target));
+
+ var tooltip = Assert.IsType(target.GetValue(ToolTip.ToolTipProperty));
+ _mouseHelper.Enter(tooltip);
+ _mouseHelper.Leave(target);
+
+ Assert.True(ToolTip.GetIsOpen(target));
+
+ _mouseHelper.Enter(target);
+ _mouseHelper.Leave(tooltip);
+
+ Assert.True(ToolTip.GetIsOpen(target));
+ }
+ }
+
+ [Fact]
+ public void Should_Close_When_Pointer_Is_Moved_From_ToolTip_To_Another_Control()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+
+ var target = new Decorator()
+ {
+ [ToolTip.TipProperty] = "Tip",
+ [ToolTip.ShowDelayProperty] = 0
+ };
+
+ var other = new Decorator();
+
+ var panel = new StackPanel
+ {
+ Children = { target, other }
+ };
+
+ window.Content = panel;
+
+ window.ApplyStyling();
+ window.ApplyTemplate();
+ window.Presenter.ApplyTemplate();
+
+ _mouseHelper.Enter(target);
+ Assert.True(ToolTip.GetIsOpen(target));
+
+ var tooltip = Assert.IsType(target.GetValue(ToolTip.ToolTipProperty));
+ _mouseHelper.Enter(tooltip);
+ _mouseHelper.Leave(target);
+
+ Assert.True(ToolTip.GetIsOpen(target));
+
+ _mouseHelper.Enter(other);
+ _mouseHelper.Leave(tooltip);
+
+ Assert.False(ToolTip.GetIsOpen(target));
+ }
+ }
}
internal class ToolTipViewModel
diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
index 70306ab91f..1bcc805f82 100644
--- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
@@ -242,6 +242,28 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(target.LogicalChildren[0], childControl);
}
+ [Fact]
+ public void Old_Content_Should_Be_Null_When_New_Content_Is_Old_one()
+ {
+ using var app = Start();
+ var (target, transition) = CreateTarget("");
+ var presenter2 = GetContentPresenters2(target);
+ target.PageTransition = null;
+
+ var childControl = new Control();
+ target.Presenter!.Content = childControl;
+
+ const string fakePage1 = "fakePage1";
+ const string fakePage2 = "fakePage2";
+
+ target.Presenter!.Content = fakePage1;
+ target.Presenter!.Content = fakePage2;
+ target.Presenter!.Content = fakePage1;
+
+ Assert.Equal(fakePage1, target.Presenter!.Content);
+ Assert.Equal(null, presenter2.Content);
+ }
+
private static IDisposable Start()
{
return UnitTestApplication.Start(
diff --git a/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs b/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
index 26ed20f982..a54fc7bf12 100644
--- a/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
+++ b/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
@@ -14,7 +14,6 @@ public class MiniCompilerTests
{
private const string AvaloniaXaml = "";
private const string MiniClass = "namespace Example { public class Valid { public int Foo() => 21; } }";
- private const string MiniInvalidXaml = "";
private const string MiniValidXaml = "";
[Fact]
@@ -27,16 +26,6 @@ public class MiniCompilerTests
Assert.NotNull(xaml.Root);
}
- [Fact]
- public void Should_Throw_When_Unable_To_Resolve_Types_From_Simple_Invalid_Markup()
- {
- var xaml = XDocumentXamlParser.Parse(MiniInvalidXaml);
- var compilation = CreateBasicCompilation(MiniClass);
- var compiler = MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation));
-
- Assert.Throws(() => compiler.Transform(xaml));
- }
-
[Fact]
public void Should_Resolve_Types_From_Simple_Avalonia_Markup()
{
@@ -56,4 +45,4 @@ public class MiniCompilerTests
.AddReferences(MetadataReference.CreateFromFile(typeof(ISupportInitialize).Assembly.Location))
.AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml b/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml
index 5e9d083be3..00eec9b969 100644
--- a/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml
+++ b/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml
@@ -8,7 +8,8 @@
+
+ Content="{x:Static NonExistent.Member}" />
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
index da05bf555c..414b9be512 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
@@ -13,8 +12,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
public AvaloniaPropertyConverterTest()
{
// Ensure properties are registered.
- var foo = Class1.FooProperty;
- var attached = AttachedOwner.AttachedProperty;
+ _ = Class1.FooProperty;
+ _ = AttachedOwner.AttachedProperty;
}
[Fact]
@@ -114,13 +113,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
public static readonly StyledProperty FooProperty =
AvaloniaProperty.Register("Foo");
-
- public ThemeVariant ThemeVariant
- {
- get => throw new NotImplementedException();
- }
-
- public event EventHandler ThemeVariantChanged;
}
private class AttachedOwner
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 468bcc5589..abc9d400fd 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -8,6 +10,7 @@ using System.Linq;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+using System.Xml;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
@@ -47,7 +50,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -73,7 +76,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -99,7 +102,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -131,7 +134,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("itemsControl");
+ var textBlock = window.GetControl("itemsControl");
var dataContext = new TestDataContext
{
@@ -161,7 +164,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
textBlock.DataContext = new TestDataContext();
Assert.Equal(TestDataContext.StaticProperty, textBlock.Text);
@@ -180,7 +183,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -205,7 +208,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -231,7 +234,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -257,7 +260,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
DelayedBinding.ApplyBindings(textBlock);
@@ -288,7 +291,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -314,7 +317,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -340,7 +343,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -370,10 +373,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
+ window.Presenter!.ApplyTemplate();
var dataContext = new TestDataContext
{
@@ -399,7 +402,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext();
@@ -428,7 +431,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext();
@@ -462,7 +465,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("target");
+ var target = window.GetControl("target");
var dataContext = new TestDataContext();
@@ -472,9 +475,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.ApplyTemplate();
target.ApplyTemplate();
- target.Presenter.UpdateChild();
+ target.Presenter!.UpdateChild();
- Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child!).Text);
}
}
@@ -496,7 +499,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
- ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@@ -514,7 +517,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
- ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ }
+ }
+
+ [Fact]
+ public void ReportsMultipleErrorsOnDataContextAndBindingPathErrors()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var ex = Assert.Throws(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.Collection(
+ ex.InnerExceptions,
+ inner => Assert.IsAssignableFrom(inner),
+ inner => Assert.IsAssignableFrom(inner),
+ inner => Assert.IsAssignableFrom(inner));
}
}
@@ -537,7 +562,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("target");
+ var target = window.GetControl("target");
var dataContext = new TestDataContext();
@@ -547,9 +572,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.ApplyTemplate();
target.ApplyTemplate();
- target.Presenter.ApplyTemplate();
+ target.Presenter!.ApplyTemplate();
- Assert.Equal(dataContext.ListProperty[0], (string)((ContentPresenter)target.Presenter.Panel.Children[0]).Content);
+ Assert.Equal(dataContext.ListProperty[0], (string?)((ContentPresenter)target.Presenter.Panel!.Children[0]).Content);
}
}
@@ -575,8 +600,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
");
- var target = window.FindControl("target");
- var column = target!.Columns.Single();
+ var target = window.GetControl("target");
+ var column = target.Columns.Single();
var dataContext = new TestDataContext();
@@ -588,13 +613,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
target.ApplyTemplate();
// Assert DataGridLikeColumn.Binding data type.
- var compiledPath = ((CompiledBindingExtension)column.Binding).Path;
+ var compiledPath = ((CompiledBindingExtension)column.Binding!).Path;
var node = Assert.IsType(Assert.Single(compiledPath.Elements));
Assert.Equal(typeof(int), node.Property.PropertyType);
// Assert DataGridLikeColumn.Template data type by evaluating the template.
var firstItem = dataContext.ListProperty[0];
- var textBlockFromTemplate = (TextBlock)column.Template.Build(firstItem);
+ var textBlockFromTemplate = (TextBlock)column.Template!.Build(firstItem)!;
textBlockFromTemplate.DataContext = firstItem;
Assert.Equal(firstItem.Length.ToString(), textBlockFromTemplate.Text);
}
@@ -622,8 +647,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
");
- var target = window.FindControl("target");
- var column = target!.Columns.Single();
+ var target = window.GetControl("target");
+ var column = target.Columns.Single();
var dataContext = new TestDataContext();
dataContext.ListProperty.Add("Test");
@@ -633,13 +658,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
target.ApplyTemplate();
// Assert DataGridLikeColumn.Binding data type.
- var compiledPath = ((CompiledBindingExtension)column.Binding).Path;
+ var compiledPath = ((CompiledBindingExtension)column.Binding!).Path;
var node = Assert.IsType(Assert.Single(compiledPath.Elements));
Assert.Equal(typeof(int), node.Property.PropertyType);
// Assert DataGridLikeColumn.Template data type by evaluating the template.
var firstItem = dataContext.ListProperty[0];
- var textBlockFromTemplate = (TextBlock)column.Template.Build(firstItem);
+ var textBlockFromTemplate = (TextBlock)column.Template!.Build(firstItem)!;
textBlockFromTemplate.DataContext = firstItem;
Assert.Equal(firstItem.Length.ToString(), textBlockFromTemplate.Text);
}
@@ -663,7 +688,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
- ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@@ -684,7 +709,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("target");
+ var target = window.GetControl("target");
var dataContext = new TestDataContext();
@@ -694,9 +719,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.ApplyTemplate();
target.ApplyTemplate();
- target.Presenter.UpdateChild();
+ target.Presenter!.UpdateChild();
- Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child!).Text);
}
}
@@ -717,7 +742,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("target");
+ var target = window.GetControl("target");
var dataContext = new TestDataContext();
@@ -727,9 +752,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.ApplyTemplate();
target.ApplyTemplate();
- target.Presenter.UpdateChild();
+ target.Presenter!.UpdateChild();
- Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child!).Text);
}
}
@@ -750,7 +775,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("target");
+ var target = window.GetControl("target");
var dataContext = new TestDataContext();
@@ -760,9 +785,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.ApplyTemplate();
target.ApplyTemplate();
- target.Presenter.UpdateChild();
+ target.Presenter!.UpdateChild();
- Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child!).Text);
}
}
@@ -782,7 +807,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("text2");
+ var textBlock = window.GetControl("text2");
var dataContext = new TestDataContext
{
@@ -811,7 +836,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("text2");
+ var textBlock = window.GetControl("text2");
var dataContext = new TestDataContext
{
@@ -838,10 +863,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("text");
+ var target = window.GetControl("text");
window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
+ window.Presenter!.ApplyTemplate();
target.ApplyTemplate();
Assert.Equal("test", target.Text);
@@ -862,10 +887,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("text");
+ var target = window.GetControl("text");
window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
+ window.Presenter!.ApplyTemplate();
target.ApplyTemplate();
//Assert.Equal("test", target.Text);
@@ -1013,10 +1038,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var target = window.FindControl("text");
+ var target = window.GetControl("text");
window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
+ window.Presenter!.ApplyTemplate();
target.ApplyTemplate();
Assert.Equal("Test".Length.ToString(), target.Text);
@@ -1040,7 +1065,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
@@ -1064,7 +1089,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
@@ -1089,7 +1114,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
@@ -1115,7 +1140,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
@@ -1135,7 +1160,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
Assert.Equal(Brushes.Red.Color, contentControl.Content);
}
@@ -1155,7 +1180,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -1181,7 +1206,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x:CompileBindings='true'>
";
- ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@@ -1198,7 +1223,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
Assert.Equal("foo", contentControl.Content);
}
@@ -1218,7 +1243,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
window.DataContext = new TestDataContext() { StringProperty = "Foo" };
@@ -1240,7 +1265,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
window.DataContext = new TestDataContext() { StringProperty = "Foo" };
@@ -1260,7 +1285,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x:DataType='local:TestDataContext'
x:CompileBindings='notabool'>
";
- ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@@ -1277,7 +1302,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
var dataContext = new TestDataContext();
@@ -1300,7 +1325,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
var dataContext = "foo";
@@ -1323,7 +1348,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
var dataContext = new TestDataContext
{
@@ -1349,7 +1374,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
var dataContext = new TestDataContext
{
@@ -1375,7 +1400,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
var data = new TestData()
{
@@ -1405,7 +1430,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var contentControl = window.FindControl("contentControl");
+ var contentControl = window.GetControl("contentControl");
var dataContext = new TestDataContext
{
@@ -1435,7 +1460,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -1461,7 +1486,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -1487,7 +1512,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -1513,7 +1538,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext
{
@@ -1560,10 +1585,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
+ window.Presenter!.ApplyTemplate();
Assert.Equal(textBlock.Name, textBlock.Text);
}
@@ -1587,10 +1612,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var button = window.FindControl("button");
+ var button = window.GetControl("button");
window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
+ window.Presenter!.ApplyTemplate();
Assert.True(button.IsVisible);
@@ -1622,12 +1647,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
window.DataContext = new MethodDataContext();
- Assert.IsAssignableFrom(typeof(Action), window.FindControl("action").Content);
- Assert.IsAssignableFrom(typeof(Func), window.FindControl("func").Content);
- Assert.IsAssignableFrom(typeof(Action), window.FindControl("action16").Content);
- Assert.IsAssignableFrom(typeof(Func), window.FindControl("func16").Content);
- Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl("customvoid").Content.GetType()));
- Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl("customint").Content.GetType()));
+ Assert.IsAssignableFrom(typeof(Action), window.GetControl("action").Content);
+ Assert.IsAssignableFrom(typeof(Func), window.GetControl("func").Content);
+ Assert.IsAssignableFrom(typeof(Action), window.GetControl("action16").Content);
+ Assert.IsAssignableFrom(typeof(Func), window.GetControl("func16").Content);
+ Assert.True(typeof(Delegate).IsAssignableFrom(window.GetControl("customvoid").Content!.GetType()));
+ Assert.True(typeof(Delegate).IsAssignableFrom(window.GetControl("customint").Content!.GetType()));
}
}
@@ -1644,7 +1669,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var button = window.FindControl("button");
+ var button = window.GetControl("button");
var vm = new MethodAsCommandDataContext();
button.DataContext = vm;
@@ -1669,7 +1694,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var button = window.FindControl("button");
+ var button = window.GetControl("button");
var vm = new MethodAsCommandDataContext();
button.DataContext = vm;
@@ -1694,7 +1719,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var vm = new MethodAsCommandDataContext();
textBlock.DataContext = vm;
@@ -1720,7 +1745,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var button = window.FindControl("button");
+ var button = window.GetControl("button");
var vm = new MethodAsCommandDataContext()
{
Parameter = commandParameter
@@ -1748,7 +1773,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var button = window.FindControl("button");
+ var button = window.GetControl("button");
var vm = new MethodAsCommandDataContext()
{
Parameter = null,
@@ -1780,7 +1805,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x:DataType='local:TestDataContext'
X='{CompiledBinding StringProperty}' />";
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml);
- var compiledPath = ((CompiledBindingExtension)control.X).Path;
+ var compiledPath = ((CompiledBindingExtension)control.X!).Path;
var node = Assert.IsType(Assert.Single(compiledPath.Elements));
Assert.Equal(typeof(string), node.Property.PropertyType);
@@ -1798,7 +1823,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />";
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml);
- var compiledPath = ((CompiledBindingExtension)control.X).Path;
+ var compiledPath = ((CompiledBindingExtension)control.X!).Path;
var node = Assert.IsType(Assert.Single(compiledPath.Elements));
Assert.Equal(typeof(string), node.Property.PropertyType);
@@ -1817,7 +1842,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />";
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true });
- var compiledPath = ((CompiledBindingExtension)control.X).Path;
+ var compiledPath = ((CompiledBindingExtension)control.X!).Path;
var node = Assert.IsType(Assert.Single(compiledPath.Elements));
Assert.Equal(typeof(string), node.Property.PropertyType);
@@ -1840,7 +1865,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var comboBox = window.FindControl("comboBox");
+ var comboBox = window.GetControl("comboBox");
var dataContext = new TestDataContext();
dataContext.GenericProperty.Add(123);
@@ -1867,7 +1892,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
- var textBlock = window.FindControl("textBlock");
+ var textBlock = window.GetControl("textBlock");
var dataContext = new TestDataContext();
window.DataContext = dataContext;
@@ -1944,11 +1969,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
throw new Exception("Expected " + type);
}
-
- static void ThrowsXamlParseException(Action cb) => Throws("XamlParseException", cb);
- static void ThrowsXamlTransformException(Action cb) => Throws("XamlTransformException", cb);
-
-
+
static void PerformClick(Button button)
{
button.RaiseEvent(new KeyEventArgs
@@ -1969,7 +1990,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public interface IHasProperty
{
- string StringProperty { get; set; }
+ string? StringProperty { get; set; }
}
public interface IHasPropertyDerived : IHasProperty
@@ -1984,17 +2005,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public static IValueConverter Instance { get; } = new AppendConverter();
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> string.Format("{0}+{1}+{2}", value, parameter, culture);
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
public class TestData
{
- public string StringProperty { get; set; }
+ public string? StringProperty { get; set; }
}
public class TestDataContextBaseClass {}
@@ -2002,17 +2023,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public class TestDataContext : TestDataContextBaseClass, IHasPropertyDerived, IHasExplicitProperty
{
public bool BoolProperty { get; set; }
- public string StringProperty { get; set; }
+ public string? StringProperty { get; set; }
- public Task TaskProperty { get; set; }
+ public Task? TaskProperty { get; set; }
- public IObservable ObservableProperty { get; set; }
+ public IObservable? ObservableProperty { get; set; }
public ObservableCollection ObservableCollectionProperty { get; set; } = new ObservableCollection();
- public string[] ArrayProperty { get; set; }
+ public string[]? ArrayProperty { get; set; }
- public object[] ObjectsArrayProperty { get; set; }
+ public object[]? ObjectsArrayProperty { get; set; }
public List ListProperty { get; set; } = new List();
@@ -2049,7 +2070,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public class ListItemCollectionView : List
{
- public T CurrentItem { get; set; }
+ public T? CurrentItem { get; set; }
}
public class MethodDataContext
@@ -2066,15 +2087,16 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public class MethodAsCommandDataContext : INotifyPropertyChanged
{
- public event PropertyChangedEventHandler PropertyChanged;
+ public event PropertyChangedEventHandler? PropertyChanged;
public string Method() => Value = "Called";
public string Method1(int i) => Value = $"Called {i}";
public string Method2(int i, int j) => Value = $"Called {i},{j}";
public string Value { get; private set; } = "Not called";
- object _parameter;
- public object Parameter
+ private object? _parameter;
+
+ public object? Parameter
{
get => _parameter;
set
@@ -2093,7 +2115,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Value = $"Do {parameter}";
}
- [Metadata.DependsOn(nameof(Parameter))]
+ [DependsOn(nameof(Parameter))]
public bool CanDo(object parameter)
{
return ReferenceEquals(null, Parameter) == false;
@@ -2103,22 +2125,22 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public class CustomDataTemplate : IDataTemplate
{
[DataType]
- public Type FancyDataType { get; set; }
+ public Type? FancyDataType { get; set; }
[Content]
[TemplateContent]
- public object Content { get; set; }
+ public object? Content { get; set; }
- public bool Match(object data) => FancyDataType?.IsInstanceOfType(data) ?? true;
+ public bool Match(object? data) => FancyDataType?.IsInstanceOfType(data) ?? true;
- public Control Build(object data) => TemplateContent.Load(Content)?.Result;
+ public Control? Build(object? data) => TemplateContent.Load(Content)?.Result;
}
public class CustomDataTemplateInherit : CustomDataTemplate { }
public class AssignBindingControl : Control
{
- [AssignBinding] public IBinding X { get; set; }
+ [AssignBinding] public IBinding? X { get; set; }
}
public class DataGridLikeControl : Control
@@ -2129,8 +2151,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x => x.Items,
(x, v) => x.Items = v);
- private IEnumerable _items;
- public IEnumerable Items
+ private IEnumerable? _items;
+
+ public IEnumerable? Items
{
get => _items;
set => SetAndRaise(ItemsProperty, ref _items, value);
@@ -2143,10 +2166,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
[AssignBinding]
[InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))]
- public IBinding Binding { get; set; }
+ public IBinding? Binding { get; set; }
[InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))]
- public IDataTemplate Template { get; set; }
+ public IDataTemplate? Template { get; set; }
}
public class ImplicitConvertible
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
index 9a008503f5..76ba5f9c35 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
@@ -1,8 +1,9 @@
-using System;
+#nullable enable
+
+using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Media;
-using Avalonia.Metadata;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
@@ -40,9 +41,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var userControl = Assert.IsType(compiled[1]);
- var border = userControl.FindControl("border");
+ var border = userControl.GetControl("border");
- var brush = (ISolidColorBrush)border.Background;
+ var brush = (ISolidColorBrush)border.Background!;
Assert.Equal(0xff506070, brush.Color.ToUInt32());
}
}
@@ -130,17 +131,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
private readonly Dictionary _langs = new();
- public IResourceHost Owner { get; private set; }
+ public IResourceHost? Owner { get; private set; }
public bool HasResources => true;
- public event EventHandler OwnerChanged;
+ public event EventHandler? OwnerChanged
+ {
+ add { }
+ remove { }
+ }
public void AddOwner(IResourceHost owner) => Owner = owner;
public void RemoveOwner(IResourceHost owner) => Owner = null;
- public bool TryGetResource(object key, ThemeVariant theme, out object? value)
+ public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
if (_langs.TryGetValue("English", out var res))
{
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs
new file mode 100644
index 0000000000..d873c7f9ff
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Styling;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
+
+public class AvaloniaIntrinsicsTests : XamlTestBase
+{
+ [Fact]
+ public void All_Intrinsics_Are_Parsed_And_Set()
+ {
+ var xaml = @"";
+
+ var target = AvaloniaRuntimeXamlLoader.Parse(xaml);
+
+ Assert.NotNull(target);
+ Assert.Equal(new TimeSpan(0, 10, 10), target.TimeSpanProperty);
+ Assert.Equal(new Thickness(1), target.ThicknessProperty);
+ Assert.Equal(new Thickness(1), target.ThicknessProperty);
+ Assert.Equal(new Point(15, 15), target.PointProperty);
+ Assert.Equal(new Vector(16.6, 16.6), target.VectorProperty);
+ Assert.Equal(new Size(20, 20), target.SizeProperty);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), target.MatrixProperty);
+ Assert.Equal(new CornerRadius(4), target.CornerRadiusProperty);
+ Assert.Equal(Color.Parse("#44ff11"), target.ColorProperty);
+ Assert.Equal(new RelativePoint(0.5, 0.5, RelativeUnit.Relative), target.RelativePointProperty);
+ Assert.Equal(new GridLength(10, GridUnitType.Star), target.GridLengthProperty);
+ Assert.Equal(new ImmutableSolidColorBrush(Color.Parse("#44ff11")), target.IBrushProperty);
+ Assert.Equal(TextTrimming.CharacterEllipsis, target.TextTrimmingProperty);
+ Assert.Equal(TextDecorations.Strikethrough, target.TextDecorationCollectionProperty);
+ Assert.Equal(WindowTransparencyLevel.AcrylicBlur, target.WindowTransparencyLevelProperty);
+ Assert.Equal(new Uri("https://avaloniaui.net/"), target.UriProperty);
+ Assert.Equal(ThemeVariant.Dark, target.ThemeVariantProperty);
+ Assert.Equal(new[] { new Point(1, 1), new Point(2, 2) }, target.PointsProperty);
+ }
+
+ [Fact]
+ public void All_Intrinsics_Report_Errors_If_Failed()
+ {
+ var xaml = @"";
+ // TODO: double check why we don't throw error on other supported types. Should it be warnings?
+
+ var diagnostics = new List();
+ Assert.Throws(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
+ new RuntimeXamlLoaderConfiguration
+ {
+ DiagnosticHandler = diagnostic =>
+ {
+ diagnostics.Add(diagnostic);
+ return diagnostic.Severity;
+ }
+ }));
+
+ Assert.Collection(
+ diagnostics,
+ d => AssertDiagnostic(d, "time span"),
+ d => AssertDiagnostic(d, "thickness"),
+ d => AssertDiagnostic(d, "point"),
+ d => AssertDiagnostic(d, "vector"),
+ d => AssertDiagnostic(d, "size"),
+ d => AssertDiagnostic(d, "matrix"),
+ d => AssertDiagnostic(d, "corner radius"),
+ d => AssertDiagnostic(d, "color"),
+ d => AssertDiagnostic(d, "relative point"),
+ d => AssertDiagnostic(d, "grid length"),
+ d => AssertDiagnostic(d, "points list"),
+ // Compiler attempts to parse PointsList twice - as a list and as a point.
+ d => AssertDiagnostic(d, "point"));
+
+ void AssertDiagnostic(RuntimeXamlDiagnostic runtimeXamlDiagnostic, string contains)
+ {
+ Assert.Equal(RuntimeXamlDiagnosticSeverity.Error, runtimeXamlDiagnostic.Severity);
+ Assert.Contains(contains, runtimeXamlDiagnostic.Title, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
+
+public class TestIntrinsicsControl : Control
+{
+ public TimeSpan TimeSpanProperty { get; set; }
+
+ // public FontFamily FontFamilyProperty { get; set; }
+ public Thickness ThicknessProperty { get; set; }
+ public Point PointProperty { get; set; }
+ public Vector VectorProperty { get; set; }
+ public Size SizeProperty { get; set; }
+ public Matrix MatrixProperty { get; set; }
+ public CornerRadius CornerRadiusProperty { get; set; }
+ public Color ColorProperty { get; set; }
+ public RelativePoint RelativePointProperty { get; set; }
+ public GridLength GridLengthProperty { get; set; }
+ public IBrush IBrushProperty { get; set; }
+ public TextTrimming TextTrimmingProperty { get; set; }
+ public TextDecorationCollection TextDecorationCollectionProperty { get; set; }
+ public WindowTransparencyLevel WindowTransparencyLevelProperty { get; set; }
+ public Uri UriProperty { get; set; }
+ public ThemeVariant ThemeVariantProperty { get; set; }
+ public Points PointsProperty { get; set; }
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
index 67e808221e..374c5d1544 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
@@ -277,6 +277,34 @@ public class StyleIncludeTests
Assert.IsType(control.Styles[0]);
Assert.IsType(control.Styles[1]);
}
+
+ [Fact]
+ public void Style_Inside_Resources_Should_Produce_Warning()
+ {
+ var diagnostics = new List();
+ var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@"
+
+
+
+
+
+
+
+
+"), new RuntimeXamlLoaderConfiguration
+ {
+ DiagnosticHandler = diagnostic =>
+ {
+ diagnostics.Add(diagnostic);
+ return diagnostic.Severity;
+ }
+ });
+ Assert.IsAssignableFrom(((ResourceDictionary)control.Resources)!.MergedDictionaries[0]);
+ var warning = Assert.Single(diagnostics);
+ Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
+ }
[Fact]
public void StyleInclude_From_CodeBehind_Resolves_Compiled()
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
index b04beed48d..544bb68509 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
@@ -631,5 +631,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
}
}
+
+ [Fact]
+ public void Multiple_Errors_Are_Reported()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var ex = Assert.Throws(() => (Window)AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.Collection(
+ ex.InnerExceptions,
+ inner => Assert.IsAssignableFrom(inner),
+ inner => Assert.IsAssignableFrom(inner),
+ inner => Assert.IsAssignableFrom(inner));
+ }
+ }
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
index 3154da8ecf..cda38f1718 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
@@ -327,6 +327,8 @@ namespace Avalonia.Markup.Xaml.UnitTests
[Fact]
public void Style_Parser_Throws_For_Duplicate_Setter()
{
+ using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
+
var xaml = @"
";
- AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
- e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
+ var diagnostics = new List();
+ // We still have a runtime check in the StyleInstance class, but in this test we only care about compile warnings.
+ Assert.Throws(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
+ {
+ LocalAssembly = typeof(XamlIlTests).Assembly,
+ DiagnosticHandler = diagnostic =>
+ {
+ diagnostics.Add(diagnostic);
+ return diagnostic.Severity;
+ }
+ }));
+ var warning = Assert.Single(diagnostics);
+ Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
+ Assert.StartsWith("Duplicate setter encountered for property 'Height'", warning.Title);
}
[Fact]
public void Control_Theme_Parser_Throws_For_Duplicate_Setter()
{
+ using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
+
var xaml = @"
";
- AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
- e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
+ var diagnostics = new List();
+ // We still have a runtime check in the StyleInstance class, but in this test we only care about compile warnings.
+ Assert.Throws(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
+ {
+ LocalAssembly = typeof(XamlIlTests).Assembly,
+ DiagnosticHandler = diagnostic =>
+ {
+ diagnostics.Add(diagnostic);
+ return diagnostic.Severity;
+ }
+ }));
+ var warning = Assert.Single(diagnostics);
+ Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
+ Assert.StartsWith("Duplicate setter encountered for property 'Height'", warning.Title);
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs
index c23f01db07..c36f2a2531 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs
@@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
if(e is XmlException)
return;
+ throw new Exception("Expected to throw xaml exception", e);
}
throw new Exception("Expected to throw xaml exception");
}
diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
index 131f5ec18a..1b16617f87 100644
--- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs
+++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
@@ -1,16 +1,16 @@
+#nullable enable
+
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
-using Avalonia.Controls.Shapes;
-using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Xunit;
using Path = System.IO.Path;
+
#pragma warning disable CS0649
#if AVALONIA_SKIA
@@ -69,7 +69,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var fmt = new PixelFormat(fmte);
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, new PixelSize(80, 80));
- var r = Avalonia.AvaloniaLocator.Current.GetRequiredService();
+ var r = AvaloniaLocator.Current.GetRequiredService();
using(var cpuContext = r.CreateBackendContext(null))
using (var target = cpuContext.CreateRenderTarget(new object[] { fb }))
using (var ctx = target.CreateDrawingContext())
@@ -94,7 +94,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var rc = new Rect(0, 0, 60, 60);
ctx.DrawBitmap(bmp.PlatformImpl, 1, rc, rc);
}
- rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png"));
+ rtb.Save(Path.Combine(OutputPath, testName + ".out.png"));
}
CompareImagesNoRenderer(testName);
}
@@ -123,7 +123,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var name = nameof(WriteableBitmapShouldBeUsable) + "_" + fmt;
- writeableBitmap.Save(System.IO.Path.Combine(OutputPath, name + ".out.png"));
+ writeableBitmap.Save(Path.Combine(OutputPath, name + ".out.png"));
CompareImagesNoRenderer(name);
}
@@ -177,7 +177,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var testName = nameof(BitmapsShouldSupportTranscoders_Lenna) + "_" + formatName + names[step];
- var path = System.IO.Path.Combine(OutputPath, testName + ".out.png");
+ var path = Path.Combine(OutputPath, testName + ".out.png");
fixed (byte* pData = data)
{
Bitmap? b = null;
diff --git a/tests/Avalonia.RenderTests/Media/TileBrushTests.cs b/tests/Avalonia.RenderTests/Media/TileBrushTests.cs
index c171573be7..c8ec8483ac 100644
--- a/tests/Avalonia.RenderTests/Media/TileBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/TileBrushTests.cs
@@ -44,7 +44,6 @@ public class DrawingBrushTests: TestBase
#if AVALONIA_SKIA
[Fact]
-#endif
public async Task DrawingBrushIsProperlyUpscaled()
{
Decorator target = new Decorator
@@ -66,6 +65,7 @@ public class DrawingBrushTests: TestBase
await RenderToFile(target);
CompareImages();
}
+#endif
GeometryDrawing CreateDrawing()
{
diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs
index 237f3f2189..ad84b897fb 100644
--- a/tests/Avalonia.RenderTests/TestBase.cs
+++ b/tests/Avalonia.RenderTests/TestBase.cs
@@ -83,7 +83,7 @@ namespace Avalonia.Direct2D1.RenderTests
get;
}
- protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "", double dpi = 96)
+ protected Task RenderToFile(Control target, [CallerMemberName] string testName = "", double dpi = 96)
{
if (!Directory.Exists(OutputPath))
{
@@ -124,6 +124,8 @@ namespace Avalonia.Direct2D1.RenderTests
}
writableBitmap.Save(compositedPath);
}
+
+ return Task.CompletedTask;
}
class BitmapFramebufferSurface : IFramebufferPlatformSurface
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 92901987be..f80c89e23d 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -1,8 +1,9 @@
-using System;
+#nullable enable
+
+using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
-using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.UnitTests;
@@ -30,10 +31,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
Assert.Single(textLine.TextRuns);
var textRun = textLine.TextRuns[0];
+ Assert.NotNull(textRun.Properties);
+
Assert.Equal(defaultProperties.Typeface, textRun.Properties.Typeface);
Assert.Equal(defaultProperties.ForegroundBrush, textRun.Properties.ForegroundBrush);
@@ -57,6 +62,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
Assert.Equal(5, textLine.TextRuns.Count);
Assert.Equal(50, textLine.Length);
@@ -120,6 +127,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
Assert.Equal(5, textLine.TextRuns.Count);
Assert.Equal(14, textLine.Length);
@@ -153,6 +162,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
Assert.Equal(text.Length, textLine.Length);
for (var i = 0; i < GenericTextRunPropertiesRuns.Length; i++)
@@ -186,6 +197,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
Assert.Equal(numberOfRuns, textLine.TextRuns.Count);
}
}
@@ -207,6 +220,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
Assert.Equal(1, textLine.TextRuns.Count);
}
}
@@ -228,6 +243,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
+ Assert.NotNull(textLine);
+
var firstRun = textLine.TextRuns[0];
Assert.Equal(4, firstRun.Length);
@@ -249,7 +266,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var formatter = new TextFormatterImpl();
- var line = formatter.FormatLine(textSource, 0, 33, paragraphProperties);
+ formatter.FormatLine(textSource, 0, 33, paragraphProperties);
textSource = new SingleBufferTextSource(text, defaultProperties);
@@ -262,6 +279,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, currentPosition, 1, paragraphProperties);
+ Assert.NotNull(textLine);
+
if (text.Length - currentPosition > expectedCharactersPerLine)
{
Assert.Equal(expectedCharactersPerLine, textLine.Length);
@@ -318,6 +337,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, currentPosition, paragraphWidth,
new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.Wrap));
+ Assert.NotNull(textLine);
+
var end = textLine.FirstTextSourceIndex + textLine.Length - 1;
Assert.True(expected.Contains(end));
@@ -351,6 +372,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties, lineHeight: 50));
+ Assert.NotNull(textLine);
+
Assert.Equal(50, textLine.Height);
}
}
@@ -381,6 +404,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, textSourceIndex, 200, paragraphProperties);
+ Assert.NotNull(textLine);
+
Assert.True(textLine.Width <= 200);
textSourceIndex += textLine.Length;
@@ -407,6 +432,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, textSourceIndex, 3, paragraphProperties);
+ Assert.NotNull(textLine);
+
Assert.NotEqual(0, textLine.Length);
textSourceIndex += textLine.Length;
@@ -454,6 +481,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, currentPosition, 300,
new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow));
+ Assert.NotNull(textLine);
+
currentPosition += textLine.Length;
if (textLine.Width > 300 || currentHeight + textLine.Height > 240)
@@ -502,6 +531,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, 100, paragraphProperties);
+ Assert.NotNull(textLine);
+
var expectedOffset = 0d;
switch (textAlignment)
@@ -534,13 +565,15 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var formatter = new TextFormatterImpl();
var textPosition = 87;
- TextLineBreak lastBreak = null;
+ TextLineBreak? lastBreak = null;
while (textPosition < text.Length)
{
var textLine =
formatter.FormatLine(textSource, textPosition, 50, paragraphProperties, lastBreak);
+ Assert.NotNull(textLine);
+
Assert.Equal(textLine.Length, textLine.TextRuns.Sum(x => x.Length));
textPosition += textLine.Length;
@@ -564,6 +597,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, 33, paragraphProperties);
+ Assert.NotNull(textLine);
+
var remainingRunsLineBreak = Assert.IsType(textLine.TextLineBreak);
var remainingRuns = remainingRunsLineBreak.AcquireRemainingRuns();
Assert.NotNull(remainingRuns);
@@ -592,6 +627,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var expectedTextLine = formatter.FormatLine(new SingleBufferTextSource(text, defaultProperties),
0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(expectedTextLine);
+
var expectedRuns = expectedTextLine.TextRuns.Cast().ToList();
var expectedGlyphs = expectedRuns
@@ -613,6 +650,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(textLine);
+
var shapedRuns = textLine.TextRuns.Cast().ToList();
var actualGlyphs = shapedRuns
@@ -637,6 +676,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(textLine);
+
Assert.Equal(3, textLine.TextRuns.Count);
Assert.True(textLine.TextRuns[1] is RectangleRun);
@@ -655,6 +696,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(textLine);
+
Assert.NotNull(textLine.TextLineBreak);
Assert.Equal(TextRun.DefaultTextSourceLength, textLine.Length);
@@ -690,20 +733,22 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var pos = 0;
- TextLineBreak previousLineBreak = null;
- TextLine textLine = null;
+ TextLineBreak? previousLineBreak = null;
+ TextLine? textLine = null;
while (pos < text.Length)
{
textLine = TextFormatter.Current.FormatLine(textSource, pos, 30, paragraphProperties, previousLineBreak);
+ Assert.NotNull(textLine);
+
pos += textLine.Length;
previousLineBreak = textLine.TextLineBreak;
}
Assert.NotNull(textLine);
-
+ Assert.NotNull(textLine.TextLineBreak);
Assert.NotNull(textLine.TextLineBreak.TextEndOfLine);
}
}
@@ -729,6 +774,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(textLine);
+
void VerifyHit(int offset)
{
var glyphCenter = textLine.GetTextBounds(offset, 1)[0].Rectangle.Center;
@@ -756,6 +803,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(textLine);
+
var bounds = textLine.GetTextBounds(0, 3);
Assert.Equal(1, bounds.Count);
@@ -779,6 +828,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var source = new ListTextSource(new RectangleRun(new Rect(0, 0, 200, 10), Brushes.Aqua));
var textLine = TextFormatter.Current.FormatLine(source, 0, 100, paragraphProperties);
+ Assert.NotNull(textLine);
Assert.Equal(200d, textLine.WidthIncludingTrailingWhitespace);
}
}
@@ -822,6 +872,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
if (textLine.TextLineBreak is {} eol && eol.TextEndOfLine is TextEndOfParagraph)
break;
}
+
+ Assert.NotEmpty(lines);
}
}
@@ -832,13 +884,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
DefaultTextRunProperties = defaultTextRunProperties;
}
- public override FlowDirection FlowDirection { get; }
- public override TextAlignment TextAlignment { get; }
- public override double LineHeight { get; }
- public override bool FirstLineInParagraph { get; }
+ public override FlowDirection FlowDirection => default;
+ public override TextAlignment TextAlignment => default;
+ public override double LineHeight => default;
+ public override bool FirstLineInParagraph => default;
public override TextRunProperties DefaultTextRunProperties { get; }
- public override TextWrapping TextWrapping { get; }
- public override double Indent { get; }
+ public override TextWrapping TextWrapping => default;
+ public override double Indent => default;
public override double DefaultIncrementalTab => 64;
}
@@ -858,12 +910,12 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var source = new ListTextSource(text);
var textLine = TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paragraphProperties);
+ Assert.NotNull(textLine);
var backspaceHit = textLine.GetBackspaceCaretCharacterHit(new CharacterHit(2));
Assert.Equal(1, backspaceHit.FirstCharacterIndex);
Assert.Equal(0, backspaceHit.TrailingLength);
}
-
}
[Fact]
@@ -878,6 +930,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(new SimpleTextSource(text, defaultRunProperties), 0, 120, paragraphProperties);
+ Assert.NotNull(textLine);
Assert.Equal(3, textLine.TextRuns.Count);
}
}
@@ -913,7 +966,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
private class EmptyTextSource : ITextSource
{
- public TextRun GetTextRun(int textSourceIndex)
+ public TextRun? GetTextRun(int textSourceIndex)
{
return null;
}
@@ -936,7 +989,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
_text = text;
}
- public TextRun GetTextRun(int textSourceIndex)
+ public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _text.Length + TextRun.DefaultTextSourceLength + _text.Length)
{
@@ -954,7 +1007,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
private class ListTextSource : ITextSource
{
- private List _runs = new();
+ private readonly List