diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 76620e8b93..73e38f8cb9 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -23,8 +23,6 @@
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
- "src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
- "src\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
@@ -33,6 +31,8 @@
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
+ "src\\Headless\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
+ "src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
@@ -65,4 +65,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
-}
\ No newline at end of file
+}
diff --git a/Avalonia.sln b/Avalonia.sln
index f33b782479..d4ccdfdc69 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -181,9 +181,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Headless\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
EndProject
@@ -260,6 +260,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.Desktop", "sam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.iOS", "samples\SafeAreaDemo.iOS\SafeAreaDemo.iOS.csproj", "{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -599,6 +605,14 @@ Global
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -690,6 +704,10 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
+ {F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
+ {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 73954c7f4d..e8d4baba11 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,5 +1,5 @@
-
+
$(DefineConstants);NET7SDK
diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm
index fdc144e3a5..6d1ff7cf12 100644
--- a/native/Avalonia.Native/src/OSX/AvnView.mm
+++ b/native/Avalonia.Native/src/OSX/AvnView.mm
@@ -12,7 +12,6 @@
{
ComPtr _parent;
NSTrackingArea* _area;
- NSMutableAttributedString* _markedText;
bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed;
AvnInputModifiers _modifierState;
NSEvent* _lastMouseDownEvent;
@@ -22,8 +21,9 @@
AvnPlatformResizeReason _resizeReason;
AvnAccessibilityElement* _accessibilityChild;
NSRect _cursorRect;
- NSMutableString* _text;
- NSRange _selection;
+ NSMutableAttributedString* _text;
+ NSRange _selectedRange;
+ NSRange _markedRange;
}
- (void)onClosed
@@ -59,6 +59,11 @@
[self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
_modifierState = AvnInputModifiersNone;
+
+ _text = [[NSMutableAttributedString alloc] initWithString:@""];
+ _markedRange = NSMakeRange(0, 0);
+ _selectedRange = NSMakeRange(0, 0);
+
return self;
}
@@ -521,9 +526,13 @@
- (void)keyDown:(NSEvent *)event
{
- [self keyboardEvent:event withType:KeyDown];
- _lastKeyHandled = [[self inputContext] handleEvent:event];
- [super keyDown:event];
+ _lastKeyHandled = false;
+
+ [[self inputContext] handleEvent:event];
+
+ if(!_lastKeyHandled){
+ [self keyboardEvent:event withType:KeyDown];
+ }
}
- (void)keyUp:(NSEvent *)event
@@ -532,6 +541,10 @@
[super keyUp:event];
}
+- (void) doCommandBySelector:(SEL)selector{
+
+}
+
- (AvnInputModifiers)getModifiers:(NSEventModifierFlags)mod
{
unsigned int rv = 0;
@@ -561,50 +574,52 @@
- (BOOL)hasMarkedText
{
- return [_markedText length] > 0;
+ return _markedRange.length > 0;
}
- (NSRange)markedRange
{
- if([_markedText length] > 0)
- return NSMakeRange(0, [_markedText length] - 1);
- return NSMakeRange(NSNotFound, 0);
+ return _markedRange;
}
- (NSRange)selectedRange
{
- return _selection;
+ return _selectedRange;
}
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
{
+ _lastKeyHandled = true;
+
+ NSString* markedText;
+
if([string isKindOfClass:[NSAttributedString class]])
{
- _markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
+ markedText = [string string];
}
else
{
- _markedText = [[NSMutableAttributedString alloc] initWithString:string];
+ markedText = (NSString*) string;
}
- if(!_parent->InputMethod->IsActive()){
- return;
+ _markedRange = NSMakeRange(_selectedRange.location, [markedText length]);
+
+ if(_parent->InputMethod->IsActive()){
+ _parent->InputMethod->Client->SetPreeditText((char*)[markedText UTF8String]);
}
-
- _parent->InputMethod->Client->SetPreeditText((char*)[_markedText.string UTF8String]);
}
- (void)unmarkText
{
- [[_markedText mutableString] setString:@""];
+ if(_parent->InputMethod->IsActive()){
+ _parent->InputMethod->Client->SetPreeditText(nullptr);
+ }
- [[self inputContext] discardMarkedText];
+ _markedRange = NSMakeRange(_selectedRange.location, 0);
- if(!_parent->InputMethod->IsActive()){
- return;
+ if([self inputContext]) {
+ [[self inputContext] discardMarkedText];
}
-
- _parent->InputMethod->Client->SetPreeditText(nullptr);
}
- (NSArray *)validAttributesForMarkedText
@@ -614,19 +629,38 @@
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
- return nullptr;
+ if(actualRange){
+ range = *actualRange;
+ }
+
+ NSAttributedString* subString = [_text attributedSubstringFromRange:range];
+
+ return subString;
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
- [self unmarkText];
-
- if(_parent != nullptr)
+ if(_parent == nullptr){
+ return;
+ }
+
+ NSString* text;
+
+ if([string isKindOfClass:[NSAttributedString class]])
{
- _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]);
+ text = [string string];
+ }
+ else
+ {
+ text = (NSString*) string;
}
- [[self inputContext] invalidateCharacterCoordinates];
+ [self unmarkText];
+
+ uint32_t timestamp = static_cast([NSDate timeIntervalSinceReferenceDate] * 1000);
+
+ _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [text UTF8String]);
+
}
- (NSUInteger)characterIndexForPoint:(NSPoint)point
@@ -746,15 +780,11 @@
}
- (void) setText:(NSString *)text{
- [_text setString:text];
-
- [[self inputContext] discardMarkedText];
+ [[_text mutableString] setString:text];
}
- (void) setSelection:(int)start :(int)end{
- _selection = NSMakeRange(start, end - start);
-
- [[self inputContext] invalidateCharacterCoordinates];
+ _selectedRange = NSMakeRange(start, end - start);
}
- (void) setCursorRect:(AvnRect)rect{
@@ -766,7 +796,9 @@
_cursorRect = windowRectOnScreen;
- [[self inputContext] invalidateCharacterCoordinates];
+ if([self inputContext]) {
+ [[self inputContext] invalidateCharacterCoordinates];
+ }
}
@end
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 40232947d9..e17bad28d7 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -212,6 +212,7 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
+ RunCoreTest("Avalonia.Headless.UnitTests");
});
Target RunRenderTests => _ => _
@@ -273,6 +274,8 @@ partial class Build : NukeBuild
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
new NumergeNukeLogger()))
throw new Exception("Package merge failed");
+ RefAssemblyGenerator.GenerateRefAsmsInPackage(Parameters.NugetRoot / "Avalonia." +
+ Parameters.Version + ".nupkg");
});
Target RunTests => _ => _
diff --git a/nukebuild/Helpers.cs b/nukebuild/Helpers.cs
new file mode 100644
index 0000000000..d8d06559bf
--- /dev/null
+++ b/nukebuild/Helpers.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+using Nuke.Common.Utilities;
+
+class Helpers
+{
+ public static IDisposable UseTempDir(out string dir)
+ {
+ var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ Directory.CreateDirectory(path);
+ dir = path;
+ return DelegateDisposable.CreateBracket(null, () =>
+ {
+ try
+ {
+ Directory.Delete(path, true);
+ }
+ catch
+ {
+ // ignore
+ }
+ });
+ }
+}
diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs
new file mode 100644
index 0000000000..cbe5236bca
--- /dev/null
+++ b/nukebuild/RefAssemblyGenerator.cs
@@ -0,0 +1,171 @@
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using ILRepacking;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+public class RefAssemblyGenerator
+{
+ class Resolver : DefaultAssemblyResolver, IAssemblyResolver
+ {
+ private readonly string _dir;
+ Dictionary _cache = new();
+
+ public Resolver(string dir)
+ {
+ _dir = dir;
+ }
+
+ public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
+ {
+ if (_cache.TryGetValue(name.Name, out var asm))
+ return asm;
+ var path = Path.Combine(_dir, name.Name + ".dll");
+ if (File.Exists(path))
+ return _cache[name.Name] = AssemblyDefinition.ReadAssembly(path, parameters);
+ return base.Resolve(name, parameters);
+ }
+ }
+
+ public static void PatchRefAssembly(string file)
+ {
+ var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk");
+ var snk = new byte[reader.Length];
+ reader.Read(snk, 0, snk.Length);
+
+ var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters
+ {
+ ReadWrite = true,
+ InMemory = true,
+ ReadSymbols = true,
+ SymbolReaderProvider = new DefaultSymbolReaderProvider(false),
+ AssemblyResolver = new Resolver(Path.GetDirectoryName(file))
+ });
+
+ var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule,
+ def.MainModule.TypeSystem.CoreLibrary));
+ var obsoleteCtor = def.MainModule.ImportReference(new MethodReference(".ctor",
+ def.MainModule.TypeSystem.Void, obsoleteAttribute)
+ {
+ Parameters = { new ParameterDefinition(def.MainModule.TypeSystem.String) }
+ });
+
+ foreach(var t in def.MainModule.Types)
+ ProcessType(t, obsoleteCtor);
+ def.Write(file, new WriterParameters()
+ {
+ StrongNameKeyBlob = snk,
+ WriteSymbols = def.MainModule.HasSymbols,
+ SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(),
+ DeterministicMvid = def.MainModule.HasSymbols
+ });
+ }
+
+ static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor)
+ {
+ foreach (var nested in type.NestedTypes)
+ ProcessType(nested, obsoleteCtor);
+ if (type.IsInterface)
+ {
+ var hideMethods = type.Name.EndsWith("Impl")
+ || (type.HasCustomAttributes && type.CustomAttributes.Any(a =>
+ a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute"));
+
+ var injectMethod = hideMethods
+ || type.CustomAttributes.Any(a =>
+ a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute");
+
+ if (hideMethods)
+ {
+ foreach (var m in type.Methods)
+ {
+ var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
+ MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
+ m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
+ }
+ }
+
+ if(injectMethod)
+ {
+ type.Methods.Add(new MethodDefinition("NotClientImplementable",
+ MethodAttributes.Assembly
+ | MethodAttributes.Abstract
+ | MethodAttributes.NewSlot
+ | MethodAttributes.HideBySig, type.Module.TypeSystem.Void));
+ }
+
+ var forceUnstable = type.CustomAttributes.Any(a =>
+ a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute");
+
+ foreach (var m in type.Methods)
+ MarkAsUnstable(m, obsoleteCtor, forceUnstable);
+ foreach (var m in type.Properties)
+ MarkAsUnstable(m, obsoleteCtor, forceUnstable);
+ foreach (var m in type.Events)
+ MarkAsUnstable(m, obsoleteCtor, forceUnstable);
+
+ }
+ }
+
+ static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force)
+ {
+ if (!force && (
+ def.HasCustomAttributes == false
+ || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")))
+ return;
+
+ if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
+ return;
+
+ def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor)
+ {
+ ConstructorArguments =
+ {
+ new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String,
+ "This is a part of unstable API and can be changed in minor releases. You have been warned")
+ }
+ });
+ }
+
+ public static void GenerateRefAsmsInPackage(string packagePath)
+ {
+ using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
+ ZipArchiveMode.Update))
+ {
+ foreach (var entry in archive.Entries.ToList())
+ {
+ if (entry.FullName.StartsWith("ref/"))
+ entry.Delete();
+ }
+
+ foreach (var entry in archive.Entries.ToList())
+ {
+ if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml"))
+ {
+ var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4),
+ CompressionLevel.Optimal);
+ using (var src = entry.Open())
+ using (var dst = newEntry.Open())
+ src.CopyTo(dst);
+ }
+ }
+
+ var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll"))
+ .Select((e => new { s = e.FullName.Split('/'), e = e }))
+ .Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e })
+ .GroupBy(x => x.Tfm);
+ foreach(var tfm in libs)
+ using (Helpers.UseTempDir(out var temp))
+ {
+ foreach (var l in tfm)
+ l.Entry.ExtractToFile(Path.Combine(temp, l.Name));
+ foreach (var l in tfm)
+ PatchRefAssembly(Path.Combine(temp, l.Name));
+ foreach (var l in tfm)
+ archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index 13bac4b7db..d03746766e 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -31,18 +31,11 @@
-
-
-
-
-
-
-
+
+
-
-
-
+
diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index e465e9caf3..877d475fb6 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/samples/ControlCatalog.NetCore/Properties/launchSettings.json b/samples/ControlCatalog.NetCore/Properties/launchSettings.json
index 5964ca320e..11feb94bb3 100644
--- a/samples/ControlCatalog.NetCore/Properties/launchSettings.json
+++ b/samples/ControlCatalog.NetCore/Properties/launchSettings.json
@@ -6,6 +6,10 @@
"Dxgi": {
"commandName": "Project",
"commandLineArgs": "--dxgi"
+ },
+ "VNC": {
+ "commandName": "Project",
+ "commandLineArgs": "--vnc"
}
}
-}
\ No newline at end of file
+}
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index 3b847adcbb..64bf3e53b3 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -26,8 +26,6 @@
#FFFFFFFF
- #FF0078D7
- #FF005A9E
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 9c439c874f..9c511f9eb0 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -19,13 +19,9 @@ namespace ControlCatalog
{
public class MainView : UserControl
{
- private readonly IPlatformSettings _platformSettings;
-
public MainView()
{
AvaloniaXamlLoader.Load(this);
- _platformSettings = AvaloniaLocator.Current.GetRequiredService();
- PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
var sideBar = this.Get("Sidebar");
@@ -141,50 +137,6 @@ namespace ControlCatalog
ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
};
}
-
- _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
- PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
- }
-
- protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- base.OnDetachedFromLogicalTree(e);
-
- _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
- }
-
- private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
- {
- Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
- Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
- Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
- Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
- Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3);
- Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5);
- Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7);
-
- static Color ChangeColorLuminosity(Color color, double luminosityFactor)
- {
- var red = (double)color.R;
- var green = (double)color.G;
- var blue = (double)color.B;
-
- if (luminosityFactor < 0)
- {
- luminosityFactor = 1 + luminosityFactor;
- red *= luminosityFactor;
- green *= luminosityFactor;
- blue *= luminosityFactor;
- }
- else if (luminosityFactor >= 0)
- {
- red = (255 - red) * luminosityFactor + red;
- green = (255 - green) * luminosityFactor + green;
- blue = (255 - blue) * luminosityFactor + blue;
- }
-
- return new Color(color.A, (byte)red, (byte)green, (byte)blue);
- }
}
}
}
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 95378ed717..d8d9678a2d 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -170,10 +170,21 @@
-
-
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index c9c7939c1c..39497f1811 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -270,6 +270,8 @@ namespace IntegrationTestApp
this.Get("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.Get("ClickedMenuItem").Text = "None";
+ if (source?.Name == "ResetSliders")
+ this.Get("HorizontalSlider").Value = 50;
if (source?.Name == "ShowTransparentWindow")
ShowTransparentWindow();
if (source?.Name == "ShowTransparentPopup")
diff --git a/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj b/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
index a24e55de81..31a6b05175 100644
--- a/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
+++ b/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 126c488d59..fae1aacf61 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -91,7 +91,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
@@ -156,12 +156,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
protected virtual void OnResized(Size size)
{
- Resized?.Invoke(size, PlatformResizeReason.Unspecified);
+ Resized?.Invoke(size, WindowResizeReason.Unspecified);
}
internal void Resize(Size size)
{
- Resized?.Invoke(size, PlatformResizeReason.Layout);
+ Resized?.Invoke(size, WindowResizeReason.Layout);
}
class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo
diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
index e350a019d4..8c731c188f 100644
--- a/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Collections
/// Indicates if a weak subscription should be used to track changes to the collection.
///
/// A disposable used to terminate the subscription.
- internal static IDisposable ForEachItem(
+ public static IDisposable ForEachItem(
this IAvaloniaReadOnlyDictionary collection,
Action added,
Action removed,
diff --git a/src/Avalonia.Base/Controls/IResourceDictionary.cs b/src/Avalonia.Base/Controls/IResourceDictionary.cs
index 2bd1f65638..6712498bf4 100644
--- a/src/Avalonia.Base/Controls/IResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/IResourceDictionary.cs
@@ -18,6 +18,6 @@ namespace Avalonia.Controls
///
/// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
///
- IDictionary ThemeDictionaries { get; }
+ IDictionary ThemeDictionaries { get; }
}
}
diff --git a/src/Avalonia.Base/Controls/IThemeVariantProvider.cs b/src/Avalonia.Base/Controls/IThemeVariantProvider.cs
new file mode 100644
index 0000000000..d1dca2efbf
--- /dev/null
+++ b/src/Avalonia.Base/Controls/IThemeVariantProvider.cs
@@ -0,0 +1,22 @@
+using Avalonia.Metadata;
+using Avalonia.Styling;
+
+namespace Avalonia.Controls;
+
+///
+/// Resource provider with theme variant awareness.
+/// Can be used with .
+///
+///
+/// This is a helper interface for the XAML compiler to make Key property accessibly by the markup extensions.
+/// Which means, it can only be used with ResourceDictionaries and markup extensions in the XAML code.
+/// This API might be removed in the future minor updates.
+///
+[Unstable]
+public interface IThemeVariantProvider : IResourceProvider
+{
+ ///
+ /// Key property set by the compiler.
+ ///
+ ThemeVariant? Key { get; set; }
+}
diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs
index 231a19baab..b928cf0672 100644
--- a/src/Avalonia.Base/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs
@@ -13,13 +13,13 @@ namespace Avalonia.Controls
///
/// An indexed dictionary of resources.
///
- public class ResourceDictionary : IResourceDictionary
+ public class ResourceDictionary : IResourceDictionary, IThemeVariantProvider
{
private object? lastDeferredItemKey;
private Dictionary
diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
index c091d07632..90153d3293 100644
--- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
@@ -875,10 +875,11 @@ namespace Avalonia.Controls
{
if (_textBox != null)
{
+ SetCurrentValue(TextProperty, String.Empty);
+
if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
{
DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
- SetCurrentValue(TextProperty, string.Empty);
_defaultText = string.Empty;
var watermarkFormat = "<{0}>";
string watermarkText;
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index 3a4ae80cf4..387357dddd 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -47,7 +47,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
set
{
_clientSize = value;
- Resized?.Invoke(value, PlatformResizeReason.Unspecified);
+ Resized?.Invoke(value, WindowResizeReason.Unspecified);
}
}
@@ -65,7 +65,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action? Input { get; set; }
public Action? Paint { get; set; }
- public Action? Resized { get; set; }
+ public Action? Resized { get; set; }
public Action? ScalingChanged { get; set; }
public Action? TransparencyLevelChanged { get; set; }
diff --git a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
index 4c844ce30f..2ff74cc582 100644
--- a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
+++ b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
@@ -3,7 +3,7 @@ using Avalonia.Metadata;
namespace Avalonia.Platform
{
- [Unstable]
+ [Unstable, PrivateApi]
public interface IPlatformIconLoader
{
IWindowIconImpl LoadIcon(string fileName);
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
index 29156f4030..bb6b2304af 100644
--- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
+++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
@@ -9,40 +9,6 @@ using Avalonia.Rendering;
namespace Avalonia.Platform
{
- ///
- /// Describes the reason for a message.
- ///
- public enum PlatformResizeReason
- {
- ///
- /// The resize reason is unknown or unspecified.
- ///
- Unspecified,
-
- ///
- /// The resize was due to the user resizing the window, for example by dragging the
- /// window frame.
- ///
- User,
-
- ///
- /// The resize was initiated by the application, for example by setting one of the sizing-
- /// related properties on such as or
- /// .
- ///
- Application,
-
- ///
- /// The resize was initiated by the layout system.
- ///
- Layout,
-
- ///
- /// The resize was due to a change in DPI.
- ///
- DpiChange,
- }
-
///
/// Defines a platform-specific top-level window implementation.
///
@@ -93,7 +59,7 @@ namespace Avalonia.Platform
///
/// Gets or sets a method called when the toplevel is resized.
///
- Action? Resized { get; set; }
+ Action? Resized { get; set; }
///
/// Gets or sets a method called when the toplevel's scaling changes.
diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs
index 31b144ce00..5591e68235 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -114,7 +114,7 @@ namespace Avalonia.Platform
///
/// The new client size.
/// The reason for the resize.
- void Resize(Size clientSize, PlatformResizeReason reason = PlatformResizeReason.Application);
+ void Resize(Size clientSize, WindowResizeReason reason = WindowResizeReason.Application);
///
/// Sets the client size of the top level.
diff --git a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs
index 5acc5adccd..f6cf8c604e 100644
--- a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs
+++ b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs
@@ -2,7 +2,7 @@ using Avalonia.Metadata;
namespace Avalonia.Platform
{
- [Unstable]
+ [Unstable, PrivateApi]
public interface IWindowingPlatform
{
IWindowImpl CreateWindow();
diff --git a/src/Avalonia.Controls/Platform/ScreenHelper.cs b/src/Avalonia.Controls/Platform/ScreenHelper.cs
index 0bd2be69d0..59b29b4748 100644
--- a/src/Avalonia.Controls/Platform/ScreenHelper.cs
+++ b/src/Avalonia.Controls/Platform/ScreenHelper.cs
@@ -1,11 +1,12 @@
using System.Collections.Generic;
+using Avalonia.Controls;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Platform
{
- public static class ScreenHelper
+ internal static class ScreenHelper
{
public static Screen? ScreenFromPoint(PixelPoint point, IReadOnlyList screens)
{
diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs
index c205830bc2..9854bdbea6 100644
--- a/src/Avalonia.Controls/Primitives/Thumb.cs
+++ b/src/Avalonia.Controls/Primitives/Thumb.cs
@@ -1,4 +1,6 @@
using System;
+using Avalonia.Automation.Peers;
+using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -45,6 +47,8 @@ namespace Avalonia.Controls.Primitives
remove { RemoveHandler(DragCompletedEvent, value); }
}
+ protected override AutomationPeer OnCreateAutomationPeer() => new ThumbAutomationPeer(this);
+
protected virtual void OnDragStarted(VectorEventArgs e)
{
}
@@ -81,7 +85,7 @@ namespace Avalonia.Controls.Primitives
{
if (_lastPoint.HasValue)
{
- var point = e.GetPosition(this.GetVisualParent());
+ var point = e.GetPosition(null);
var ev = new VectorEventArgs
{
RoutedEvent = DragDeltaEvent,
@@ -96,7 +100,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
e.Handled = true;
- _lastPoint = e.GetPosition(this.GetVisualParent());
+ _lastPoint = e.GetPosition(null);
var ev = new VectorEventArgs
{
@@ -119,7 +123,7 @@ namespace Avalonia.Controls.Primitives
var ev = new VectorEventArgs
{
RoutedEvent = DragCompletedEvent,
- Vector = (Vector)e.GetPosition(this.GetVisualParent()),
+ Vector = (Vector)e.GetPosition(null),
};
RaiseEvent(ev);
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
index 8dc19eb1d4..530f28fbb6 100644
--- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
@@ -38,7 +38,7 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty PullDirectionProperty =
+ internal static readonly StyledProperty PullDirectionProperty =
AvaloniaProperty.Register(nameof(PullDirection), PullDirection.TopToBottom);
///
diff --git a/src/Avalonia.Controls/Shapes/Polygon.cs b/src/Avalonia.Controls/Shapes/Polygon.cs
index 70a45f3516..78def84448 100644
--- a/src/Avalonia.Controls/Shapes/Polygon.cs
+++ b/src/Avalonia.Controls/Shapes/Polygon.cs
@@ -13,10 +13,15 @@ namespace Avalonia.Controls.Shapes
AffectsGeometry(PointsProperty);
}
+ public Polygon()
+ {
+ Points = new Points();
+ }
+
public IList Points
{
- get { return GetValue(PointsProperty); }
- set { SetValue(PointsProperty, value); }
+ get => GetValue(PointsProperty);
+ set => SetValue(PointsProperty, value);
}
protected override Geometry CreateDefiningGeometry()
diff --git a/src/Avalonia.Controls/Shapes/Polyline.cs b/src/Avalonia.Controls/Shapes/Polyline.cs
index 4b4bb3ffd0..2533794f89 100644
--- a/src/Avalonia.Controls/Shapes/Polyline.cs
+++ b/src/Avalonia.Controls/Shapes/Polyline.cs
@@ -14,10 +14,15 @@ namespace Avalonia.Controls.Shapes
AffectsGeometry(PointsProperty);
}
+ public Polyline()
+ {
+ Points = new Points();
+ }
+
public IList Points
{
- get { return GetValue(PointsProperty); }
- set { SetValue(PointsProperty, value); }
+ get => GetValue(PointsProperty);
+ set => SetValue(PointsProperty, value);
}
protected override Geometry CreateDefiningGeometry()
diff --git a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs
index 64a883e88c..895ce53907 100644
--- a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs
+++ b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs
@@ -18,10 +18,10 @@ namespace Avalonia.Controls.Templates
{
}
- public new ControlTemplateResult Build(TemplatedControl param)
+ public new TemplateResult Build(TemplatedControl param)
{
var (control, scope) = BuildWithNameScope(param);
- return new ControlTemplateResult(control, scope);
+ return new(control, scope);
}
}
}
diff --git a/src/Avalonia.Controls/Templates/IControlTemplate.cs b/src/Avalonia.Controls/Templates/IControlTemplate.cs
index 38ad6561ab..c3f9c9e8aa 100644
--- a/src/Avalonia.Controls/Templates/IControlTemplate.cs
+++ b/src/Avalonia.Controls/Templates/IControlTemplate.cs
@@ -5,23 +5,7 @@ namespace Avalonia.Controls.Templates
///
/// Interface representing a template used to build a .
///
- public interface IControlTemplate : ITemplate
+ public interface IControlTemplate : ITemplate?>
{
}
-
- public class ControlTemplateResult : TemplateResult
- {
- public Control Control { get; }
-
- public ControlTemplateResult(Control control, INameScope nameScope) : base(control, nameScope)
- {
- Control = control;
- }
-
- public new void Deconstruct(out Control control, out INameScope scope)
- {
- control = Control;
- scope = NameScope;
- }
- }
}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 07b1e9b51f..8a1cdf3f80 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -432,10 +432,13 @@ namespace Avalonia.Controls
IStyleHost IStyleHost.StylingParent => _globalStyles!;
+ ///
+ /// File System storage service used for file pickers and bookmarks.
+ ///
public IStorageProvider StorageProvider => _storageProvider
??= AvaloniaLocator.Current.GetService()?.CreateProvider(this)
?? PlatformImpl?.TryGetFeature()
- ?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
+ ?? new NoopStorageProvider();
public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature();
@@ -569,7 +572,7 @@ namespace Avalonia.Controls
///
/// The new client size.
/// The reason for the resize.
- protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason)
+ internal virtual void HandleResized(Size clientSize, WindowResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl!.FrameSize;
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index f9593f1c1b..48edb81b16 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -227,7 +227,7 @@ namespace Avalonia.Controls
impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size);
impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged;
- this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application));
+ this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, WindowResizeReason.Application));
PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
}
@@ -700,7 +700,7 @@ namespace Avalonia.Controls
if (initialSize != ClientSize)
{
- PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
+ PlatformImpl?.Resize(initialSize, WindowResizeReason.Layout);
}
LayoutManager.ExecuteInitialLayoutPass();
@@ -778,7 +778,7 @@ namespace Avalonia.Controls
if (initialSize != ClientSize)
{
- PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
+ PlatformImpl?.Resize(initialSize, WindowResizeReason.Layout);
}
LayoutManager.ExecuteInitialLayoutPass();
@@ -975,7 +975,7 @@ namespace Avalonia.Controls
protected sealed override Size ArrangeSetBounds(Size size)
{
- PlatformImpl?.Resize(size, PlatformResizeReason.Layout);
+ PlatformImpl?.Resize(size, WindowResizeReason.Layout);
return ClientSize;
}
@@ -994,7 +994,7 @@ namespace Avalonia.Controls
}
///
- protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason)
+ internal override void HandleResized(Size clientSize, WindowResizeReason reason)
{
if (ClientSize != clientSize || double.IsNaN(Width) || double.IsNaN(Height))
{
@@ -1005,8 +1005,8 @@ namespace Avalonia.Controls
// to the requested size.
if (sizeToContent != SizeToContent.Manual &&
CanResize &&
- reason == PlatformResizeReason.Unspecified ||
- reason == PlatformResizeReason.User)
+ reason == WindowResizeReason.Unspecified ||
+ reason == WindowResizeReason.User)
{
if (clientSize.Width != ClientSize.Width)
sizeToContent &= ~SizeToContent.Width;
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 814a9b5960..c2523207e4 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -80,6 +80,11 @@ namespace Avalonia.Controls
///
public event EventHandler? PositionChanged;
+ ///
+ /// Occurs when the window is resized.
+ ///
+ public event EventHandler? Resized;
+
public new IWindowBaseImpl? PlatformImpl => (IWindowBaseImpl?) base.PlatformImpl;
///
@@ -155,6 +160,15 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Trys to get the platform handle for the window.
+ ///
+ ///
+ /// An describing the window handle, or null if the handle
+ /// could not be retrieved.
+ ///
+ public IPlatformHandle? TryGetPlatformHandle() => PlatformImpl?.Handle;
+
///
/// Ensures that the window is initialized.
///
@@ -188,6 +202,12 @@ namespace Avalonia.Controls
base.OnOpened(e);
}
+ ///
+ /// Raises the event.
+ ///
+ /// An that contains the event data.
+ protected virtual void OnResized(WindowResizedEventArgs e) => Resized?.Invoke(this, e);
+
protected override void HandleClosed()
{
using (FreezeVisibilityChangeHandling())
@@ -208,7 +228,7 @@ namespace Avalonia.Controls
///
/// The new client size.
/// The reason for the resize.
- protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
+ internal override void HandleResized(Size clientSize, WindowResizeReason reason)
{
FrameSize = PlatformImpl?.FrameSize;
@@ -218,6 +238,8 @@ namespace Avalonia.Controls
LayoutManager.ExecuteLayoutPass();
Renderer.Resized(clientSize);
}
+
+ OnResized(new WindowResizedEventArgs(clientSize, reason));
}
///
diff --git a/src/Avalonia.Controls/WindowResizedEventArgs.cs b/src/Avalonia.Controls/WindowResizedEventArgs.cs
new file mode 100644
index 0000000000..daa8aa0f09
--- /dev/null
+++ b/src/Avalonia.Controls/WindowResizedEventArgs.cs
@@ -0,0 +1,61 @@
+using System;
+using Avalonia.Layout;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Describes the reason for a event.
+ ///
+ public enum WindowResizeReason
+ {
+ ///
+ /// The resize reason is unknown or unspecified.
+ ///
+ Unspecified,
+
+ ///
+ /// The resize was due to the user resizing the window, for example by dragging the
+ /// window frame.
+ ///
+ User,
+
+ ///
+ /// The resize was initiated by the application, for example by setting one of the sizing-
+ /// related properties on such as or
+ /// .
+ ///
+ Application,
+
+ ///
+ /// The resize was initiated by the layout system.
+ ///
+ Layout,
+
+ ///
+ /// The resize was due to a change in DPI.
+ ///
+ DpiChange,
+ }
+
+ ///
+ /// Provides data for the event.
+ ///
+ public class WindowResizedEventArgs : EventArgs
+ {
+ internal WindowResizedEventArgs(Size clientSize, WindowResizeReason reason)
+ {
+ ClientSize = clientSize;
+ Reason = reason;
+ }
+
+ ///
+ /// Gets the new client size of the window in device-independent pixels.
+ ///
+ public Size ClientSize { get; }
+
+ ///
+ /// Gets the reason for the resize.
+ ///
+ public WindowResizeReason Reason { get; }
+ }
+}
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index 2da8f38ea9..e0fcf8e530 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -59,7 +59,7 @@ namespace Avalonia.DesignerSupport.Remote
base.OnMessage(transport, obj);
}
- public void Resize(Size clientSize, PlatformResizeReason reason)
+ public void Resize(Size clientSize, WindowResizeReason reason)
{
_transport.Send(new RequestViewportResizeMessage
{
diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
index 85605ccd9d..313063269b 100644
--- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
+++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
@@ -179,17 +179,9 @@ namespace Avalonia.DesignerSupport.Remote
var entryPoint = asm.EntryPoint;
if (entryPoint == null)
throw Die($"Assembly {args.AppPath} doesn't have an entry point");
- var builderMethod = entryPoint.DeclaringType.GetMethod(
- BuilderMethodName,
- BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
- null,
- Array.Empty(),
- null);
- if (builderMethod == null)
- throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
+ Log($"Obtaining AppBuilder instance from {entryPoint.DeclaringType!.FullName}");
+ var appBuilder = AppBuilder.Configure(entryPoint.DeclaringType);
Design.IsDesignMode = true;
- Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}");
- var appBuilder = builderMethod.Invoke(null, null);
Log($"Initializing application in design mode");
var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer));
transport = initializer.ConfigureApp(transport, args, appBuilder);
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index ea427e4c92..f6f5c185e9 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -32,7 +32,7 @@ namespace Avalonia.DesignerSupport.Remote
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Func Closing { get; set; }
public Action Closed { get; set; }
@@ -59,7 +59,7 @@ namespace Avalonia.DesignerSupport.Remote
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent,
(_, size, __) =>
{
- Resize(size, PlatformResizeReason.Unspecified);
+ Resize(size, WindowResizeReason.Unspecified);
}));
}
@@ -112,7 +112,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
- public void Resize(Size clientSize, PlatformResizeReason reason)
+ public void Resize(Size clientSize, WindowResizeReason reason)
{
}
diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
index a25bb68458..8b2b38bb82 100644
--- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
+++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Avalonia.Logging;
+using Avalonia.Media;
using Avalonia.Platform;
using Tmds.DBus.SourceGenerator;
@@ -9,7 +10,10 @@ namespace Avalonia.FreeDesktop
internal class DBusPlatformSettings : DefaultPlatformSettings
{
private readonly OrgFreedesktopPortalSettings? _settings;
+
private PlatformColorValues? _lastColorValues;
+ private PlatformThemeVariant? _themeVariant;
+ private Color? _accentColor;
public DBusPlatformSettings()
{
@@ -21,24 +25,33 @@ namespace Avalonia.FreeDesktop
_ = TryGetInitialValueAsync();
}
- public override PlatformColorValues GetColorValues()
- {
- return _lastColorValues ?? base.GetColorValues();
- }
+ public override PlatformColorValues GetColorValues() => _lastColorValues ?? base.GetColorValues();
private async Task TryGetInitialValueAsync()
{
try
{
var value = await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme");
- _lastColorValues = GetColorValuesFromSetting(value);
- OnColorValuesChanged(_lastColorValues);
+ _themeVariant = ReadAsColorScheme(value);
+ }
+ catch (Exception ex)
+ {
+ Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get org.freedesktop.appearance.color-scheme value", ex);
+ }
+
+ try
+ {
+ var value = await _settings!.ReadAsync("org.kde.kdeglobals.General", "AccentColor");
+ _accentColor = ReadAsAccentColor(value);
}
catch (Exception ex)
{
- _lastColorValues = base.GetColorValues();
- Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex);
+ Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get org.kde.kdeglobals.General.AccentColor value", ex);
}
+
+ _lastColorValues = BuildPlatformColorValues();
+ if (_lastColorValues is not null)
+ OnColorValuesChanged(_lastColorValues);
}
private void SettingsChangedHandler(Exception? exception, (string @namespace, string key, DBusVariantItem value) valueTuple)
@@ -46,25 +59,48 @@ namespace Avalonia.FreeDesktop
if (exception is not null)
return;
- if (valueTuple is ("org.freedesktop.appearance", "color-scheme", { } value))
+ switch (valueTuple)
{
- /*
- 0: No preference
- 1: Prefer dark appearance
- 2: Prefer light appearance
- */
- _lastColorValues = GetColorValuesFromSetting(value);
- OnColorValuesChanged(_lastColorValues);
+ case ("org.freedesktop.appearance", "color-scheme", { } colorScheme):
+ _themeVariant = ReadAsColorScheme(colorScheme);
+ _lastColorValues = BuildPlatformColorValues();
+ OnColorValuesChanged(_lastColorValues!);
+ break;
+ case ("org.kde.kdeglobals.General", "AccentColor", { } accentColor):
+ _accentColor = ReadAsAccentColor(accentColor);
+ _lastColorValues = BuildPlatformColorValues();
+ OnColorValuesChanged(_lastColorValues!);
+ break;
}
}
- private static PlatformColorValues GetColorValuesFromSetting(DBusVariantItem value)
+ private PlatformColorValues? BuildPlatformColorValues()
+ {
+ if (_themeVariant is { } themeVariant && _accentColor is { } accentColor)
+ return new PlatformColorValues { ThemeVariant = themeVariant, AccentColor1 = accentColor };
+ if (_themeVariant is { } themeVariant1)
+ return new PlatformColorValues { ThemeVariant = themeVariant1 };
+ if (_accentColor is { } accentColor1)
+ return new PlatformColorValues { AccentColor1 = accentColor1 };
+ return null;
+ }
+
+ private static PlatformThemeVariant ReadAsColorScheme(DBusVariantItem value)
{
+ /*
+ 0: No preference
+ 1: Prefer dark appearance
+ 2: Prefer light appearance
+ */
var isDark = ((value.Value as DBusVariantItem)!.Value as DBusUInt32Item)!.Value == 1;
- return new PlatformColorValues
- {
- ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light
- };
+ return isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light;
+ }
+
+ private static Color ReadAsAccentColor(DBusVariantItem value)
+ {
+ var colorStr = ((value.Value as DBusVariantItem)!.Value as DBusStringItem)!.Value;
+ var rgb = colorStr.Split(',');
+ return new Color(255, byte.Parse(rgb[0]), byte.Parse(rgb[1]), byte.Parse(rgb[2]));
}
}
}
diff --git a/src/Avalonia.Headless/Avalonia.Headless.csproj b/src/Avalonia.Headless/Avalonia.Headless.csproj
deleted file mode 100644
index 95f7b79009..0000000000
--- a/src/Avalonia.Headless/Avalonia.Headless.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- net6.0;netstandard2.0
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj
index 095662a538..e69c39a41e 100644
--- a/src/Avalonia.Native/Avalonia.Native.csproj
+++ b/src/Avalonia.Native/Avalonia.Native.csproj
@@ -26,8 +26,4 @@
-
-
-
-
diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs
index 0953527284..6b7f7e8883 100644
--- a/src/Avalonia.Native/PopupImpl.cs
+++ b/src/Avalonia.Native/PopupImpl.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Controls;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Native.Interop;
using Avalonia.Platform;
@@ -29,7 +30,7 @@ namespace Avalonia.Native
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Position = position;
- Resize(size, PlatformResizeReason.Layout);
+ Resize(size, WindowResizeReason.Layout);
//TODO: We ignore the scaling override for now
}
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 0dff46057e..26c3da9d50 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -95,7 +95,7 @@ namespace Avalonia.Native
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
- Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout);
+ Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), WindowResizeReason.Layout);
}
public IAvnWindowBase Native => _native;
@@ -160,7 +160,7 @@ namespace Avalonia.Native
public Action LostFocus { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup();
@@ -211,7 +211,7 @@ namespace Avalonia.Native
{
var s = new Size(size->Width, size->Height);
_parent._savedLogicalSize = s;
- _parent.Resized?.Invoke(s, (PlatformResizeReason)reason);
+ _parent.Resized?.Invoke(s, (WindowResizeReason)reason);
}
}
@@ -360,7 +360,7 @@ namespace Avalonia.Native
}
}
- public void Resize(Size clientSize, PlatformResizeReason reason)
+ public void Resize(Size clientSize, WindowResizeReason reason)
{
_native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason);
}
diff --git a/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml b/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
deleted file mode 100644
index 0fb3ab73c2..0000000000
--- a/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- #FF0078D7
- #FF005A9E
- #FF004275
- #FF002642
- #FF429CE3
- #FF76B9ED
- #FFA6D8FF
-
diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml
new file mode 100644
index 0000000000..362d543646
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+ #FFFFFFFF
+ #33FFFFFF
+ #99FFFFFF
+ #CCFFFFFF
+ #66FFFFFF
+ #FF000000
+ #33000000
+ #99000000
+ #CC000000
+ #66000000
+ #FF171717
+ #FF000000
+ #33000000
+ #66000000
+ #CC000000
+ #FFCCCCCC
+ #FF7A7A7A
+ #FFCCCCCC
+ #FFF2F2F2
+ #FFE6E6E6
+ #FFF2F2F2
+ #FFFFFFFF
+ #FF767676
+ #19000000
+ #33000000
+ #C50500
+ #FFFFFFFF
+ #17000000
+ #2E000000
+
+
+ #FF000000
+ #33000000
+ #99000000
+ #CC000000
+ #66000000
+ #FFFFFFFF
+ #33FFFFFF
+ #99FFFFFF
+ #CCFFFFFF
+ #66FFFFFF
+ #FFF2F2F2
+ #FF000000
+ #33000000
+ #66000000
+ #CC000000
+ #FF333333
+ #FF858585
+ #FF767676
+ #FF171717
+ #FF1F1F1F
+ #FF2B2B2B
+ #FFFFFFFF
+ #FF767676
+ #19FFFFFF
+ #33FFFFFF
+ #FFF000
+ #FF000000
+ #18FFFFFF
+ #30FFFFFF
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml
similarity index 78%
rename from src/Avalonia.Themes.Fluent/Accents/Base.xaml
rename to src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml
index c19a4f5c09..517a80fd7e 100644
--- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml
@@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
-
fonts:Inter#Inter, $Default
14
@@ -28,39 +27,33 @@
-
-
-
- #FFFFFFFF
- #33FFFFFF
- #99FFFFFF
- #CCFFFFFF
- #66FFFFFF
- #FF000000
- #33000000
- #99000000
- #CC000000
- #66000000
- #FF171717
- #FF000000
- #33000000
- #66000000
- #CC000000
- #FFCCCCCC
- #FF7A7A7A
- #FFCCCCCC
- #FFF2F2F2
- #FFE6E6E6
- #FFF2F2F2
- #FFFFFFFF
- #FF767676
- #19000000
- #33000000
- #C50500
+ 374
+ 0,2,0,2
+ 1
+ -1,0,-1,0
+ 32
+ 64
+ 456
+ 0
+ 1
+ 0
+
+ 12,11,12,12
+ 96
+ 40
+ 758
- #17000000
- #2E000000
+
+ 0
+
+ 0,4,0,4
+
+
+ 12,0,12,0
+
+
+
-
-
-
-
-
-
-
- #FFFFFFFF
-
-
-
- 374
- 0,2,0,2
- 1
- -1,0,-1,0
- 32
- 64
- 456
- 0
- 1
- 0
-
- 12,11,12,12
- 96
- 40
- 758
-
-
- 0
-
-
- 0,4,0,4
-
-
- 12,0,12,0
+
-
- #FF000000
- #33000000
- #99000000
- #CC000000
- #66000000
- #FFFFFFFF
- #33FFFFFF
- #99FFFFFF
- #CCFFFFFF
- #66FFFFFF
- #FFF2F2F2
- #FF000000
- #33000000
- #66000000
- #CC000000
- #FF333333
- #FF858585
- #FF767676
- #FF171717
- #FF1F1F1F
- #FF2B2B2B
- #FFFFFFFF
- #FF767676
- #19FFFFFF
- #33FFFFFF
- #FFF000
-
- #18FFFFFF
- #30FFFFFF
-
-
-
-
-
-
-
- #FF000000
-
-
- 374
- 0,2,0,2
- 1
- -1,0,-1,0
- 32
- 64
- 456
- 0
- 1
- 0
-
- 12,11,12,12
- 96
- 40
- 758
-
-
- 0
-
-
- 0,4,0,4
-
-
- 12,0,12,0
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml
index a9bc622221..61a74f26a4 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml
@@ -4,8 +4,8 @@
-
-
+
+
@@ -52,7 +52,8 @@
-
+
@@ -291,15 +292,17 @@
ResourceKey="SystemControlHighlightBaseHighBrush" />
-
+
-
-
+
+
@@ -309,13 +312,17 @@
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -470,8 +481,8 @@
-
-
+
+
@@ -502,8 +513,8 @@
-
-
+
+
@@ -701,8 +712,9 @@
-
-
+
+
@@ -775,8 +787,8 @@
-
-
+
+
@@ -823,7 +835,8 @@
-
+
@@ -1065,14 +1078,17 @@
ResourceKey="SystemControlHighlightBaseHighBrush" />
-
-
+
+
-
-
+
+
@@ -1082,13 +1098,17 @@
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -1243,8 +1267,8 @@
-
-
+
+
@@ -1275,12 +1299,12 @@
-
-
+
+
-
-
+
+
@@ -1476,8 +1500,8 @@
-
-
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs
new file mode 100644
index 0000000000..a4ef15f950
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs
@@ -0,0 +1,163 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Styling;
+
+namespace Avalonia.Themes.Fluent.Accents;
+
+internal class SystemAccentColors : IResourceProvider
+{
+ public const string AccentKey = "SystemAccentColor";
+ public const string AccentDark1Key = "SystemAccentColorDark1";
+ public const string AccentDark2Key = "SystemAccentColorDark2";
+ public const string AccentDark3Key = "SystemAccentColorDark3";
+ public const string AccentLight1Key = "SystemAccentColorLight1";
+ public const string AccentLight2Key = "SystemAccentColorLight2";
+ public const string AccentLight3Key = "SystemAccentColorLight3";
+
+ private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215);
+ private readonly IPlatformSettings? _platformSettings;
+ private bool _invalidateColors = true;
+ private Color _systemAccentColor;
+ private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3;
+ private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3;
+
+ public SystemAccentColors()
+ {
+ _platformSettings = AvaloniaLocator.Current.GetService();
+ }
+
+ public bool HasResources => true;
+ public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
+ {
+ if (key is string strKey)
+ {
+ if (strKey.Equals(AccentKey, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColor;
+ return true;
+ }
+
+ if (strKey.Equals(AccentDark1Key, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColorDark1;
+ return true;
+ }
+
+ if (strKey.Equals(AccentDark2Key, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColorDark2;
+ return true;
+ }
+
+ if (strKey.Equals(AccentDark3Key, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColorDark3;
+ return true;
+ }
+
+ if (strKey.Equals(AccentLight1Key, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColorLight1;
+ return true;
+ }
+
+ if (strKey.Equals(AccentLight2Key, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColorLight2;
+ return true;
+ }
+
+ if (strKey.Equals(AccentLight3Key, StringComparison.InvariantCulture))
+ {
+ EnsureColors();
+ value = _systemAccentColorLight3;
+ return true;
+ }
+ }
+
+ value = null;
+ return false;
+ }
+
+ public IResourceHost? Owner { get; private set; }
+ public event EventHandler? OwnerChanged;
+ public void AddOwner(IResourceHost owner)
+ {
+ if (Owner != owner)
+ {
+ Owner = owner;
+ OwnerChanged?.Invoke(this, EventArgs.Empty);
+
+ if (_platformSettings is not null)
+ {
+ _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
+ }
+ }
+ }
+
+ public void RemoveOwner(IResourceHost owner)
+ {
+ if (Owner == owner)
+ {
+ Owner = null;
+ OwnerChanged?.Invoke(this, EventArgs.Empty);
+
+ if (_platformSettings is not null)
+ {
+ _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
+ }
+ }
+ }
+
+ private void EnsureColors()
+ {
+ if (_invalidateColors)
+ {
+ _invalidateColors = false;
+
+ _systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor;
+ (_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3,
+ _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor);
+ }
+ }
+
+ public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor)
+ {
+ // dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255
+ const double dark1step = 28.5 / 255d;
+ const double dark2step = 49 / 255d;
+ const double dark3step = 74.5 / 255d;
+ // light1step = (SystemAccentColorLight1.L - hslAccent.L) * 255
+ const double light1step = 39 / 255d;
+ const double light2step = 70 / 255d;
+ const double light3step = 103 / 255d;
+
+ var hslAccent = accentColor.ToHsl();
+
+ return (
+ // Darker shades
+ new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark1step).ToRgb(),
+ new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark2step).ToRgb(),
+ new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark3step).ToRgb(),
+
+ // Lighter shades
+ new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light1step).ToRgb(),
+ new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light2step).ToRgb(),
+ new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light3step).ToRgb()
+ );
+ }
+
+ private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
+ {
+ _invalidateColors = true;
+ Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
+ }
+}
diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs
new file mode 100644
index 0000000000..366af8e227
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs
@@ -0,0 +1,158 @@
+using Avalonia.Media;
+
+namespace Avalonia.Themes.Fluent;
+
+public partial class ColorPaletteResources
+{
+ private bool _hasAccentColor;
+ private Color _accentColor;
+ private Color _accentColorDark1, _accentColorDark2, _accentColorDark3;
+ private Color _accentColorLight1, _accentColorLight2, _accentColorLight3;
+
+ public static readonly DirectProperty AccentProperty
+ = AvaloniaProperty.RegisterDirect(nameof(Accent), r => r.Accent, (r, v) => r.Accent = v);
+
+ ///
+ /// Gets or sets the Accent color value.
+ ///
+ public Color Accent
+ {
+ get => _accentColor;
+ set => SetAndRaise(AccentProperty, ref _accentColor, value);
+ }
+
+ ///
+ /// Gets or sets the AltHigh color value.
+ ///
+ public Color AltHigh { get => GetColor("SystemAltHighColor"); set => SetColor("SystemAltHighColor", value); }
+
+ ///
+ /// Gets or sets the AltLow color value.
+ ///
+ public Color AltLow { get => GetColor("SystemAltLowColor"); set => SetColor("SystemAltLowColor", value); }
+
+ ///
+ /// Gets or sets the AltMedium color value.
+ ///
+ public Color AltMedium { get => GetColor("SystemAltMediumColor"); set => SetColor("SystemAltMediumColor", value); }
+
+ ///
+ /// Gets or sets the AltMediumHigh color value.
+ ///
+ public Color AltMediumHigh { get => GetColor("SystemAltMediumHighColor"); set => SetColor("SystemAltMediumHighColor", value); }
+
+ ///
+ /// Gets or sets the AltMediumLow color value.
+ ///
+ public Color AltMediumLow { get => GetColor("SystemAltMediumLowColor"); set => SetColor("SystemAltMediumLowColor", value); }
+
+ ///
+ /// Gets or sets the BaseHigh color value.
+ ///
+ public Color BaseHigh { get => GetColor("SystemBaseHighColor"); set => SetColor("SystemBaseHighColor", value); }
+
+ ///
+ /// Gets or sets the BaseLow color value.
+ ///
+ public Color BaseLow { get => GetColor("SystemBaseLowColor"); set => SetColor("SystemBaseLowColor", value); }
+
+ ///
+ /// Gets or sets the BaseMedium color value.
+ ///
+ public Color BaseMedium { get => GetColor("SystemBaseMediumColor"); set => SetColor("SystemBaseMediumColor", value); }
+
+ ///
+ /// Gets or sets the BaseMediumHigh color value.
+ ///
+ public Color BaseMediumHigh { get => GetColor("SystemBaseMediumHighColor"); set => SetColor("SystemBaseMediumHighColor", value); }
+
+ ///
+ /// Gets or sets the BaseMediumLow color value.
+ ///
+ public Color BaseMediumLow { get => GetColor("SystemBaseMediumLowColor"); set => SetColor("SystemBaseMediumLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeAltLow color value.
+ ///
+ public Color ChromeAltLow { get => GetColor("SystemChromeAltLowColor"); set => SetColor("SystemChromeAltLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeBlackHigh color value.
+ ///
+ public Color ChromeBlackHigh { get => GetColor("SystemChromeBlackHighColor"); set => SetColor("SystemChromeBlackHighColor", value); }
+
+ ///
+ /// Gets or sets the ChromeBlackLow color value.
+ ///
+ public Color ChromeBlackLow { get => GetColor("SystemChromeBlackLowColor"); set => SetColor("SystemChromeBlackLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeBlackMedium color value.
+ ///
+ public Color ChromeBlackMedium { get => GetColor("SystemChromeBlackMediumColor"); set => SetColor("SystemChromeBlackMediumColor", value); }
+
+ ///
+ /// Gets or sets the ChromeBlackMediumLow color value.
+ ///
+ public Color ChromeBlackMediumLow { get => GetColor("SystemChromeBlackMediumLowColor"); set => SetColor("SystemChromeBlackMediumLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeDisabledHigh color value.
+ ///
+ public Color ChromeDisabledHigh { get => GetColor("SystemChromeDisabledHighColor"); set => SetColor("SystemChromeDisabledHighColor", value); }
+
+ ///
+ /// Gets or sets the ChromeDisabledLow color value.
+ ///
+ public Color ChromeDisabledLow { get => GetColor("SystemChromeDisabledLowColor"); set => SetColor("SystemChromeDisabledLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeGray color value.
+ ///
+ public Color ChromeGray { get => GetColor("SystemChromeGrayColor"); set => SetColor("SystemChromeGrayColor", value); }
+
+ ///
+ /// Gets or sets the ChromeHigh color value.
+ ///
+ public Color ChromeHigh { get => GetColor("SystemChromeHighColor"); set => SetColor("SystemChromeHighColor", value); }
+
+ ///
+ /// Gets or sets the ChromeLow color value.
+ ///
+ public Color ChromeLow { get => GetColor("SystemChromeLowColor"); set => SetColor("SystemChromeLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeMedium color value.
+ ///
+ public Color ChromeMedium { get => GetColor("SystemChromeMediumColor"); set => SetColor("SystemChromeMediumColor", value); }
+
+ ///
+ /// Gets or sets the ChromeMediumLow color value.
+ ///
+ public Color ChromeMediumLow { get => GetColor("SystemChromeMediumLowColor"); set => SetColor("SystemChromeMediumLowColor", value); }
+
+ ///
+ /// Gets or sets the ChromeWhite color value.
+ ///
+ public Color ChromeWhite { get => GetColor("SystemChromeWhiteColor"); set => SetColor("SystemChromeWhiteColor", value); }
+
+ ///
+ /// Gets or sets the ErrorText color value.
+ ///
+ public Color ErrorText { get => GetColor("SystemErrorTextColor"); set => SetColor("SystemErrorTextColor", value); }
+
+ ///
+ /// Gets or sets the ListLow color value.
+ ///
+ public Color ListLow { get => GetColor("SystemListLowColor"); set => SetColor("SystemListLowColor", value); }
+
+ ///
+ /// Gets or sets the ListMedium color value.
+ ///
+ public Color ListMedium { get => GetColor("SystemListMediumColor"); set => SetColor("SystemListMediumColor", value); }
+
+ ///
+ /// Gets or sets the RegionColor color value.
+ ///
+ public Color RegionColor { get => GetColor("SystemRegionColor"); set => SetColor("SystemRegionColor", value); }
+}
diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResources.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResources.cs
new file mode 100644
index 0000000000..ce52f51752
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/ColorPaletteResources.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Styling;
+using Avalonia.Themes.Fluent.Accents;
+
+namespace Avalonia.Themes.Fluent;
+
+///
+/// Represents a specialized resource dictionary that contains color resources used by FluentTheme elements.
+///
+///
+/// This class can only be used in .
+///
+public partial class ColorPaletteResources : AvaloniaObject, IResourceNode
+{
+ private readonly Dictionary _colors = new(StringComparer.InvariantCulture);
+
+ public bool HasResources => _hasAccentColor || _colors.Count > 0;
+
+ public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
+ {
+ if (key is string strKey)
+ {
+ if (strKey.Equals(SystemAccentColors.AccentKey, StringComparison.InvariantCulture))
+ {
+ value = _accentColor;
+ return _hasAccentColor;
+ }
+
+ if (strKey.Equals(SystemAccentColors.AccentDark1Key, StringComparison.InvariantCulture))
+ {
+ value = _accentColorDark1;
+ return _hasAccentColor;
+ }
+
+ if (strKey.Equals(SystemAccentColors.AccentDark2Key, StringComparison.InvariantCulture))
+ {
+ value = _accentColorDark2;
+ return _hasAccentColor;
+ }
+
+ if (strKey.Equals(SystemAccentColors.AccentDark3Key, StringComparison.InvariantCulture))
+ {
+ value = _accentColorDark3;
+ return _hasAccentColor;
+ }
+
+ if (strKey.Equals(SystemAccentColors.AccentLight1Key, StringComparison.InvariantCulture))
+ {
+ value = _accentColorLight1;
+ return _hasAccentColor;
+ }
+
+ if (strKey.Equals(SystemAccentColors.AccentLight2Key, StringComparison.InvariantCulture))
+ {
+ value = _accentColorLight2;
+ return _hasAccentColor;
+ }
+
+ if (strKey.Equals(SystemAccentColors.AccentLight3Key, StringComparison.InvariantCulture))
+ {
+ value = _accentColorLight3;
+ return _hasAccentColor;
+ }
+
+ if (_colors.TryGetValue(strKey, out var color))
+ {
+ value = color;
+ return true;
+ }
+ }
+
+ value = null;
+ return false;
+ }
+
+ private Color GetColor(string key)
+ {
+ if (_colors.TryGetValue(key, out var color))
+ {
+ return color;
+ }
+
+ return default;
+ }
+
+ private void SetColor(string key, Color value)
+ {
+ if (value == default)
+ {
+ _colors.Remove(key);
+ }
+ else
+ {
+ _colors[key] = value;
+ }
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == AccentProperty)
+ {
+ _hasAccentColor = _accentColor != default;
+
+ if (_hasAccentColor)
+ {
+ (_accentColorDark1, _accentColorDark2, _accentColorDark3,
+ _accentColorLight1, _accentColorLight2, _accentColorLight3) =
+ SystemAccentColors.CalculateAccentShades(_accentColor);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs
new file mode 100644
index 0000000000..261de5497d
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs
@@ -0,0 +1,65 @@
+using System;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Styling;
+
+namespace Avalonia.Themes.Fluent;
+
+internal class ColorPaletteResourcesCollection : AvaloniaDictionary, IResourceProvider
+{
+ public ColorPaletteResourcesCollection() : base(2)
+ {
+ this.ForEachItem(
+ (_, x) =>
+ {
+ if (Owner is not null)
+ {
+ x.PropertyChanged += Palette_PropertyChanged;
+ }
+ },
+ (_, x) =>
+ {
+ if (Owner is not null)
+ {
+ x.PropertyChanged -= Palette_PropertyChanged;
+ }
+ },
+ () => throw new NotSupportedException("Dictionary reset not supported"));
+ }
+
+ public bool HasResources => Count > 0;
+ public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
+ {
+ theme ??= ThemeVariant.Default;
+ if (base.TryGetValue(theme, out var paletteResources)
+ && paletteResources.TryGetResource(key, theme, out value))
+ {
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+
+ public IResourceHost? Owner { get; private set; }
+ public event EventHandler? OwnerChanged;
+ public void AddOwner(IResourceHost owner)
+ {
+ Owner = owner;
+ OwnerChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ public void RemoveOwner(IResourceHost owner)
+ {
+ Owner = null;
+ OwnerChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void Palette_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == ColorPaletteResources.AccentProperty)
+ {
+ Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
+ }
+ }
+}
diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
index f60424a2dc..ee51ef8085 100644
--- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/Window.xaml b/src/Avalonia.Themes.Fluent/Controls/Window.xaml
index ff27cce800..8db01fa4c8 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Window.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Window.xaml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml
index e83257fd9f..0528c40c21 100644
--- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml
@@ -1,11 +1,19 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:fluent="using:Avalonia.Themes.Fluent"
+ xmlns:accents="clr-namespace:Avalonia.Themes.Fluent.Accents">
-
-
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
index 95539bc08a..5af22dbd1d 100644
--- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
@@ -31,6 +32,9 @@ namespace Avalonia.Themes.Fluent
EnsureCompactStyles();
+ Palettes = Resources.MergedDictionaries.OfType().FirstOrDefault()
+ ?? throw new InvalidOperationException("FluentTheme was initialized with missing ColorPaletteResourcesCollection.");
+
object GetAndRemove(string key)
{
var val = Resources[key]
@@ -52,6 +56,8 @@ namespace Avalonia.Themes.Fluent
set => SetValue(DensityStyleProperty, value);
}
+ public IDictionary Palettes { get; }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 38af1b6d7b..0a535d2f57 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -352,7 +352,7 @@ namespace Avalonia.X11
public IEnumerable Surfaces { get; }
public Action? Input { get; set; }
public Action? Paint { get; set; }
- public Action? Resized { get; set; }
+ public Action? Resized { get; set; }
//TODO
public Action? ScalingChanged { get; set; }
public Action? Deactivated { get; set; }
@@ -509,7 +509,7 @@ namespace Avalonia.X11
UpdateImePosition();
if (changedSize && !updatedSizeViaScaling && !_popup)
- Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified);
+ Resized?.Invoke(ClientSize, WindowResizeReason.Unspecified);
}, DispatcherPriority.Layout);
if (_useRenderWindow)
@@ -590,7 +590,7 @@ namespace Avalonia.X11
UpdateImePosition();
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize)
- Resize(oldScaledSize, true, PlatformResizeReason.DpiChange);
+ Resize(oldScaledSize, true, WindowResizeReason.DpiChange);
return true;
}
@@ -642,7 +642,7 @@ namespace Avalonia.X11
{
// Occurs once the window has been mapped, which is the earliest the extents
// can be retrieved, so invoke event to force update of TopLevel.FrameSize.
- Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified);
+ Resized?.Invoke(ClientSize, WindowResizeReason.Unspecified);
}
if (atom == _x11.Atoms._NET_WM_STATE)
@@ -959,19 +959,19 @@ namespace Avalonia.X11
}
- public void Resize(Size clientSize, PlatformResizeReason reason) => Resize(clientSize, false, reason);
+ public void Resize(Size clientSize, WindowResizeReason reason) => Resize(clientSize, false, reason);
public void Move(PixelPoint point) => Position = point;
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Move(position);
_scalingOverride = scaling;
UpdateScaling(true);
- Resize(size, true, PlatformResizeReason.Layout);
+ Resize(size, true, WindowResizeReason.Layout);
}
private PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling));
- private void Resize(Size clientSize, bool force, PlatformResizeReason reason)
+ private void Resize(Size clientSize, bool force, WindowResizeReason reason)
{
if (!force && clientSize == ClientSize)
return;
diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
index d33f773bfa..8456dc92d0 100644
--- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
+++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
@@ -74,7 +74,7 @@ namespace Avalonia.Browser
surface.Size = new PixelSize((int)newSize.Width, (int)newSize.Height);
}
- Resized?.Invoke(newSize, PlatformResizeReason.User);
+ Resized?.Invoke(newSize, WindowResizeReason.User);
(_insetsManager as BrowserInsetsManager)?.NotifySafeAreaPaddingChanged();
}
@@ -241,7 +241,7 @@ namespace Avalonia.Browser
public Action? SetCssCursor { get; set; }
public Action? Input { get; set; }
public Action? Paint { get; set; }
- public Action? Resized { get; set; }
+ public Action? Resized { get; set; }
public Action? ScalingChanged { get; set; }
public Action? TransparencyLevelChanged { get; set; }
public Action? Closed { get; set; }
diff --git a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj b/src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
similarity index 55%
rename from src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
rename to src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
index c713440dc9..1f06f28687 100644
--- a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
+++ b/src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
@@ -2,6 +2,7 @@
net6.0;netstandard2.0
+ true
@@ -9,5 +10,8 @@
-
+
+
+
+
diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
similarity index 69%
rename from src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
rename to src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
index 18c149ce2e..24703003da 100644
--- a/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
+++ b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
@@ -2,6 +2,7 @@
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
+using Avalonia.Platform;
using Avalonia.Threading;
using RemoteViewing.Vnc;
using RemoteViewing.Vnc.Server;
@@ -10,22 +11,28 @@ namespace Avalonia.Headless.Vnc
{
public class HeadlessVncFramebufferSource : IVncFramebufferSource
{
- public IHeadlessWindow Window { get; set; }
+ public Window Window { get; set; }
private object _lock = new object();
public VncFramebuffer _framebuffer = new VncFramebuffer("Avalonia", 1, 1, VncPixelFormat.RGB32);
private VncButton _previousButtons;
public HeadlessVncFramebufferSource(VncServerSession session, Window window)
{
- Window = (IHeadlessWindow)window.PlatformImpl;
+ Window = window;
session.PointerChanged += (_, args) =>
{
var pt = new Point(args.X, args.Y);
var buttons = (VncButton)args.PressedButtons;
- int TranslateButton(VncButton vncButton) =>
- vncButton == VncButton.Left ? 0 : vncButton == VncButton.Right ? 1 : 2;
+ MouseButton TranslateButton(VncButton vncButton) =>
+ vncButton switch
+ {
+ VncButton.Left => MouseButton.Left,
+ VncButton.Middle => MouseButton.Middle,
+ VncButton.Right => MouseButton.Right,
+ _ => MouseButton.None
+ };
var modifiers = (RawInputModifiers)(((int)buttons & 7) << 4);
@@ -58,34 +65,25 @@ namespace Avalonia.Headless.Vnc
private static VncButton[] CheckedButtons = new[] {VncButton.Left, VncButton.Middle, VncButton.Right};
- public VncFramebuffer Capture()
+ public unsafe VncFramebuffer Capture()
{
lock (_lock)
{
using (var bmpRef = Window.GetLastRenderedFrame())
{
- if (bmpRef?.Item == null)
+ if (bmpRef == null)
return _framebuffer;
- var bmp = bmpRef.Item;
+ var bmp = bmpRef;
if (bmp.PixelSize.Width != _framebuffer.Width || bmp.PixelSize.Height != _framebuffer.Height)
{
_framebuffer = new VncFramebuffer("Avalonia", bmp.PixelSize.Width, bmp.PixelSize.Height,
VncPixelFormat.RGB32);
}
- using (var fb = bmp.Lock())
+ var buffer = _framebuffer.GetBuffer();
+ fixed (byte* bufferPtr = buffer)
{
- var buf = _framebuffer.GetBuffer();
- if (_framebuffer.Stride == fb.RowBytes)
- Marshal.Copy(fb.Address, buf, 0, buf.Length);
- else
- for (var y = 0; y < fb.Size.Height; y++)
- {
- var sourceStart = fb.RowBytes * y;
- var dstStart = _framebuffer.Stride * y;
- var row = fb.Size.Width * 4;
- Marshal.Copy(new IntPtr(sourceStart + fb.Address.ToInt64()), buf, dstStart, row);
- }
+ bmp.CopyPixels(new PixelRect(default, bmp.PixelSize), (IntPtr)bufferPtr, buffer.Length, _framebuffer.Stride);
}
}
}
diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
similarity index 90%
rename from src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
rename to src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
index efc8c66fde..8e5cd1a316 100644
--- a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
+++ b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
@@ -1,3 +1,4 @@
+using System;
using System.Net;
using System.Net.Sockets;
using Avalonia.Controls;
@@ -25,7 +26,7 @@ namespace Avalonia
})
.AfterSetup(_ =>
{
- var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime);
+ var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!);
lt.Startup += async delegate
{
while (true)
@@ -38,7 +39,7 @@ namespace Avalonia
var session = new VncServerSession();
session.SetFramebufferSource(new HeadlessVncFramebufferSource(
- session, lt.MainWindow));
+ session, lt.MainWindow ?? throw new InvalidOperationException("MainWindow wasn't initialized")));
session.Connect(client.GetStream(), options);
}
diff --git a/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj b/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj
new file mode 100644
index 0000000000..c2c58b4f94
--- /dev/null
+++ b/src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj
@@ -0,0 +1,19 @@
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs
new file mode 100644
index 0000000000..21086fa946
--- /dev/null
+++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Avalonia.Headless.XUnit;
+
+internal class AvaloniaTestFramework : XunitTestFramework
+{
+ public AvaloniaTestFramework(IMessageSink messageSink) : base(messageSink)
+ {
+ }
+
+ protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
+ => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
+
+
+ private class Executor : XunitTestFrameworkExecutor
+ {
+ public Executor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider,
+ IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider,
+ diagnosticMessageSink)
+ {
+ }
+
+ protected override async void RunTestCases(IEnumerable testCases,
+ IMessageSink executionMessageSink,
+ ITestFrameworkExecutionOptions executionOptions)
+ {
+ executionOptions.SetValue("xunit.execution.DisableParallelization", false);
+ using (var assemblyRunner = new AvaloniaTestRunner(
+ TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink,
+ executionOptions)) await assemblyRunner.RunAsync();
+ }
+ }
+}
diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs
new file mode 100644
index 0000000000..3eace30805
--- /dev/null
+++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs
@@ -0,0 +1,45 @@
+using System.Diagnostics.CodeAnalysis;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Avalonia.Headless.XUnit;
+
+///
+/// Sets up global avalonia test framework using avalonia application builder passed as a parameter.
+///
+[TestFrameworkDiscoverer("Avalonia.Headless.XUnit.AvaloniaTestFrameworkTypeDiscoverer", "Avalonia.Headless.XUnit")]
+[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
+public sealed class AvaloniaTestFrameworkAttribute : Attribute, ITestFrameworkAttribute
+{
+ ///
+ /// Creates instance of .
+ ///
+ ///
+ /// Parameter from which should be created.
+ /// It either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application.
+ ///
+ public AvaloniaTestFrameworkAttribute(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type appBuilderEntryPointType) { }
+}
+
+///
+/// Discoverer implementation for the Avalonia testing framework.
+///
+public class AvaloniaTestFrameworkTypeDiscoverer : ITestFrameworkTypeDiscoverer
+{
+ ///
+ /// Creates instance of .
+ ///
+ public AvaloniaTestFrameworkTypeDiscoverer(IMessageSink _)
+ {
+ }
+
+ ///
+ public Type GetTestFrameworkType(IAttributeInfo attribute)
+ {
+ var builderType = attribute.GetConstructorArguments().First() as Type
+ ?? throw new InvalidOperationException("AppBuilderEntryPointType parameter must be defined on the AvaloniaTestFrameworkAttribute attribute.");
+ return typeof(AvaloniaTestFramework<>).MakeGenericType(builderType);
+ }
+}
diff --git a/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs
new file mode 100644
index 0000000000..42604adf46
--- /dev/null
+++ b/src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs
@@ -0,0 +1,61 @@
+using Avalonia.Threading;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Avalonia.Headless.XUnit;
+
+internal class AvaloniaTestRunner : XunitTestAssemblyRunner
+{
+ private CancellationTokenSource? _cancellationTokenSource;
+
+ public AvaloniaTestRunner(ITestAssembly testAssembly, IEnumerable testCases,
+ IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink,
+ ITestFrameworkExecutionOptions executionOptions) : base(testAssembly, testCases, diagnosticMessageSink,
+ executionMessageSink, executionOptions)
+ {
+ }
+
+ protected override void SetupSyncContext(int maxParallelThreads)
+ {
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = new CancellationTokenSource();
+ SynchronizationContext.SetSynchronizationContext(InitNewApplicationContext(_cancellationTokenSource.Token).Result);
+ }
+
+ public override void Dispose()
+ {
+ _cancellationTokenSource?.Cancel();
+ base.Dispose();
+ }
+
+ internal static Task InitNewApplicationContext(CancellationToken cancellationToken)
+ {
+ var tcs = new TaskCompletionSource();
+
+ new Thread(() =>
+ {
+ try
+ {
+ var appBuilder = AppBuilder.Configure(typeof(TAppBuilderEntry));
+
+ // If windowing subsystem wasn't initialized by user, force headless with default parameters.
+ if (appBuilder.WindowingSubsystemName != "Headless")
+ {
+ appBuilder = appBuilder.UseHeadless(new AvaloniaHeadlessPlatformOptions());
+ }
+
+ appBuilder.SetupWithoutStarting();
+
+ tcs.SetResult(SynchronizationContext.Current!);
+ }
+ catch (Exception e)
+ {
+ tcs.SetException(e);
+ }
+
+ Dispatcher.UIThread.MainLoop(cancellationToken);
+ }) { IsBackground = true }.Start();
+
+ return tcs.Task;
+ }
+}
diff --git a/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj b/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj
new file mode 100644
index 0000000000..b626eaeb68
--- /dev/null
+++ b/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj
@@ -0,0 +1,18 @@
+
+
+ net6.0;netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
similarity index 85%
rename from src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
rename to src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
index 90749caf6f..cefb6772c9 100644
--- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
+++ b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
@@ -1,8 +1,7 @@
using System;
using System.Diagnostics;
-using Avalonia.Reactive;
-using Avalonia.Controls;
using Avalonia.Controls.Platform;
+using Avalonia.Reactive;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
@@ -14,11 +13,12 @@ namespace Avalonia.Headless
{
public static class AvaloniaHeadlessPlatform
{
- internal static Compositor Compositor { get; private set; }
- class RenderTimer : DefaultRenderTimer
+ internal static Compositor? Compositor { get; private set; }
+
+ private class RenderTimer : DefaultRenderTimer
{
private readonly int _framesPerSecond;
- private Action _forceTick;
+ private Action? _forceTick;
protected override IDisposable StartCore(Action tick)
{
bool cancelled = false;
@@ -48,7 +48,7 @@ namespace Avalonia.Headless
public void ForceTick() => _forceTick?.Invoke();
}
- class HeadlessWindowingPlatform : IWindowingPlatform
+ private class HeadlessWindowingPlatform : IWindowingPlatform
{
public IWindowImpl CreateWindow() => new HeadlessWindowImpl(false);
@@ -56,7 +56,7 @@ namespace Avalonia.Headless
public IPopupImpl CreatePopup() => new HeadlessWindowImpl(true);
- public ITrayIconImpl CreateTrayIcon() => null;
+ public ITrayIconImpl? CreateTrayIcon() => null;
}
internal static void Initialize(AvaloniaHeadlessPlatformOptions opts)
@@ -75,7 +75,11 @@ namespace Avalonia.Headless
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null);
}
-
+ ///
+ /// Forces renderer to process a rendering timer tick.
+ /// Use this method before calling .
+ ///
+ /// Count of frames to be ticked on the timer.
public static void ForceRenderTimerTick(int count = 1)
{
var timer = AvaloniaLocator.Current.GetService() as RenderTimer;
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
similarity index 89%
rename from src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
rename to src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index f2d86a2e9d..471dc3847d 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia.Media;
@@ -16,12 +17,13 @@ namespace Avalonia.Headless
public static void Initialize()
{
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new HeadlessPlatformRenderInterface());
+ .Bind().ToConstant(new HeadlessPlatformRenderInterface())
+ .Bind().ToConstant(new HeadlessFontManagerStub());
}
public IEnumerable InstalledFontNames { get; } = new[] { "Tahoma" };
- public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
+ public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext? graphicsContext) => this;
public bool SupportsIndividualRoundRects => false;
@@ -50,7 +52,7 @@ namespace Avalonia.Headless
public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => new HeadlessRenderTarget();
public bool IsLost => false;
- public object TryGetFeature(Type featureType) => null;
+ public object? TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
@@ -129,7 +131,7 @@ namespace Avalonia.Headless
return new HeadlessGlyphRunStub();
}
- class HeadlessGlyphRunStub : IGlyphRunImpl
+ private class HeadlessGlyphRunStub : IGlyphRunImpl
{
public Rect Bounds => new Rect(new Size(8, 12));
@@ -147,7 +149,7 @@ namespace Avalonia.Headless
=> Array.Empty();
}
- class HeadlessGeometryStub : IGeometryImpl
+ private class HeadlessGeometryStub : IGeometryImpl
{
public HeadlessGeometryStub(Rect bounds)
{
@@ -160,7 +162,7 @@ namespace Avalonia.Headless
public virtual bool FillContains(Point point) => Bounds.Contains(point);
- public Rect GetRenderBounds(IPen pen)
+ public Rect GetRenderBounds(IPen? pen)
{
if(pen is null)
{
@@ -170,7 +172,7 @@ namespace Avalonia.Headless
return Bounds.Inflate(pen.Thickness / 2);
}
- public bool StrokeContains(IPen pen, Point point)
+ public bool StrokeContains(IPen? pen, Point point)
{
return false;
}
@@ -194,21 +196,21 @@ namespace Avalonia.Headless
return false;
}
- public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+ public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, [NotNullWhen(true)] out IGeometryImpl? segmentGeometry)
{
segmentGeometry = null;
return false;
}
}
- class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
+ private class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
{
public HeadlessTransformedGeometryStub(IGeometryImpl b, Matrix transform) : this(Fix(b, transform))
{
}
- static (IGeometryImpl, Matrix, Rect) Fix(IGeometryImpl b, Matrix transform)
+ private static (IGeometryImpl, Matrix, Rect) Fix(IGeometryImpl b, Matrix transform)
{
if (b is HeadlessTransformedGeometryStub transformed)
{
@@ -230,7 +232,7 @@ namespace Avalonia.Headless
public Matrix Transform { get; }
}
- class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
+ private class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
{
public HeadlessStreamingGeometryStub() : base(default)
{
@@ -246,7 +248,7 @@ namespace Avalonia.Headless
return new HeadlessStreamingGeometryContextStub(this);
}
- class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl
+ private class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl
{
private readonly HeadlessStreamingGeometryStub _parent;
private double _x1, _y1, _x2, _y2;
@@ -255,7 +257,7 @@ namespace Avalonia.Headless
_parent = parent;
}
- void Track(Point pt)
+ private void Track(Point pt)
{
if (_x1 > pt.X)
_x1 = pt.X;
@@ -304,7 +306,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl
+ private class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl
{
public Size Size { get; }
@@ -366,7 +368,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessDrawingContextStub : IDrawingContextImpl
+ private class HeadlessDrawingContextStub : IDrawingContextImpl
{
public void Dispose()
{
@@ -442,16 +444,16 @@ namespace Avalonia.Headless
}
- public object GetFeature(Type t)
+ public object? GetFeature(Type t)
{
return null;
}
- public void DrawLine(IPen pen, Point p1, Point p2)
+ public void DrawLine(IPen? pen, Point p1, Point p2)
{
}
- public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
}
@@ -469,16 +471,16 @@ namespace Avalonia.Headless
}
- public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadow = default)
+ public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadow = default)
{
}
- public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
+ public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
}
- public void DrawGlyphRun(IBrush foreground, IRef glyphRun)
+ public void DrawGlyphRun(IBrush? foreground, IRef glyphRun)
{
}
@@ -489,7 +491,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessRenderTarget : IRenderTarget
+ private class HeadlessRenderTarget : IRenderTarget
{
public void Dispose()
{
diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
similarity index 75%
rename from src/Avalonia.Headless/HeadlessPlatformStubs.cs
rename to src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
index aa400ab3e6..769fea7c6e 100644
--- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
@@ -18,17 +18,17 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
- class HeadlessClipboardStub : IClipboard
+ internal class HeadlessClipboardStub : IClipboard
{
- private string _text;
- private IDataObject _data;
+ private string? _text;
+ private IDataObject? _data;
- public Task GetTextAsync()
+ public Task GetTextAsync()
{
return Task.Run(() => _text);
}
- public Task SetTextAsync(string text)
+ public Task SetTextAsync(string? text)
{
return Task.Run(() => _text = text);
}
@@ -45,16 +45,29 @@ namespace Avalonia.Headless
public Task GetFormatsAsync()
{
- throw new NotImplementedException();
+ return Task.Run(() =>
+ {
+ if (_data is not null)
+ {
+ return _data.GetDataFormats().ToArray();
+ }
+
+ if (_text is not null)
+ {
+ return new[] { DataFormats.Text };
+ }
+
+ return Array.Empty();
+ });
}
- public async Task GetDataAsync(string format)
+ public async Task GetDataAsync(string format)
{
return await Task.Run(() => _data);
}
}
- class HeadlessCursorFactoryStub : ICursorFactory
+ internal class HeadlessCursorFactoryStub : ICursorFactory
{
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
@@ -65,7 +78,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessGlyphTypefaceImpl : IGlyphTypeface
+ internal class HeadlessGlyphTypefaceImpl : IGlyphTypeface
{
public FontMetrics Metrics => new FontMetrics
{
@@ -125,7 +138,7 @@ namespace Avalonia.Headless
public bool TryGetTable(uint tag, out byte[] table)
{
- table = null;
+ table = null!;
return false;
}
@@ -141,7 +154,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessTextShaperStub : ITextShaperImpl
+ internal class HeadlessTextShaperStub : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options)
{
@@ -153,7 +166,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessFontManagerStub : IFontManagerImpl
+ internal class HeadlessFontManagerStub : IFontManagerImpl
{
public string GetDefaultFontFamilyName()
{
@@ -179,17 +192,16 @@ namespace Avalonia.Headless
return true;
}
- public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo culture, out Typeface typeface)
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface)
{
typeface = new Typeface("Arial", fontStyle, fontWeight, fontStretch);
return true;
}
}
- class HeadlessIconLoaderStub : IPlatformIconLoader
+ internal class HeadlessIconLoaderStub : IPlatformIconLoader
{
-
- class IconStub : IWindowIconImpl
+ private class IconStub : IWindowIconImpl
{
public void Save(Stream outputStream)
{
@@ -212,7 +224,7 @@ namespace Avalonia.Headless
}
}
- class HeadlessScreensStub : IScreenImpl
+ internal class HeadlessScreensStub : IScreenImpl
{
public int ScreenCount { get; } = 1;
@@ -222,40 +234,19 @@ namespace Avalonia.Headless
new PixelRect(0, 0, 1920, 1280), true),
};
- public Screen ScreenFromPoint(PixelPoint point)
+ public Screen? ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
- public Screen ScreenFromRect(PixelRect rect)
+ public Screen? ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
- public Screen ScreenFromWindow(IWindowBaseImpl window)
+ public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
-
- internal class NoopStorageProvider : BclStorageProvider
- {
- public override bool CanOpen => false;
- public override Task> OpenFilePickerAsync(FilePickerOpenOptions options)
- {
- return Task.FromResult>(Array.Empty());
- }
-
- public override bool CanSave => false;
- public override Task SaveFilePickerAsync(FilePickerSaveOptions options)
- {
- return Task.FromResult(null);
- }
-
- public override bool CanPickFolder => false;
- public override Task> OpenFolderPickerAsync(FolderPickerOpenOptions options)
- {
- return Task.FromResult>(Array.Empty());
- }
- }
}
diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
new file mode 100644
index 0000000000..8fbc5ec6ef
--- /dev/null
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
@@ -0,0 +1,101 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Headless;
+
+///
+/// Set of extension methods to simplify usage of Avalonia.Headless platform.
+///
+public static class HeadlessWindowExtensions
+{
+ ///
+ /// Triggers a renderer timer tick and captures last rendered frame.
+ ///
+ /// Bitmap with last rendered frame. Null, if nothing was rendered.
+ public static Bitmap? CaptureRenderedFrame(this TopLevel topLevel)
+ {
+ Dispatcher.UIThread.RunJobs();
+ AvaloniaHeadlessPlatform.ForceRenderTimerTick();
+ return topLevel.GetLastRenderedFrame();
+ }
+
+ ///
+ /// Reads last rendered frame.
+ /// Note, in order to trigger rendering timer, call method.
+ ///
+ /// Bitmap with last rendered frame. Null, if nothing was rendered.
+ public static Bitmap? GetLastRenderedFrame(this TopLevel topLevel)
+ {
+ if (AvaloniaLocator.Current.GetService() is HeadlessPlatformRenderInterface)
+ {
+ throw new NotSupportedException(
+ "To capture a rendered frame, make sure that headless application was initialized with '.UseSkia()' and disabled 'UseHeadlessDrawing' in the 'AvaloniaHeadlessPlatformOptions'.");
+ }
+
+ return GetImpl(topLevel).GetLastRenderedFrame();
+ }
+
+ ///
+ /// Simulates keyboard press on the headless window/toplevel.
+ ///
+ public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers) =>
+ RunJobsAndGetImpl(topLevel).KeyPress(key, modifiers);
+
+ ///
+ /// Simulates keyboard release on the headless window/toplevel.
+ ///
+ public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers) =>
+ RunJobsAndGetImpl(topLevel).KeyRelease(key, modifiers);
+
+ ///
+ /// Simulates mouse down on the headless window/toplevel.
+ ///
+ public static void MouseDown(this TopLevel topLevel, Point point, MouseButton button,
+ RawInputModifiers modifiers = RawInputModifiers.None) =>
+ RunJobsAndGetImpl(topLevel).MouseDown(point, button, modifiers);
+
+ ///
+ /// Simulates mouse move on the headless window/toplevel.
+ ///
+ public static void MouseMove(this TopLevel topLevel, Point point,
+ RawInputModifiers modifiers = RawInputModifiers.None) =>
+ RunJobsAndGetImpl(topLevel).MouseMove(point, modifiers);
+
+ ///
+ /// Simulates mouse up on the headless window/toplevel.
+ ///
+ public static void MouseUp(this TopLevel topLevel, Point point, MouseButton button,
+ RawInputModifiers modifiers = RawInputModifiers.None) =>
+ RunJobsAndGetImpl(topLevel).MouseUp(point, button, modifiers);
+
+ ///
+ /// Simulates mouse wheel on the headless window/toplevel.
+ ///
+ public static void MouseWheel(this TopLevel topLevel, Point point, Vector delta,
+ RawInputModifiers modifiers = RawInputModifiers.None) =>
+ RunJobsAndGetImpl(topLevel).MouseWheel(point, delta, modifiers);
+
+ ///
+ /// Simulates drag'n'drop target on the headless window/toplevel.
+ ///
+ public static void DragDrop(this TopLevel topLevel, Point point, RawDragEventType type, IDataObject data,
+ DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None) =>
+ RunJobsAndGetImpl(topLevel).DragDrop(point, type, data, effects, modifiers);
+
+ private static IHeadlessWindow RunJobsAndGetImpl(this TopLevel topLevel)
+ {
+ Dispatcher.UIThread.RunJobs();
+ return GetImpl(topLevel);
+ }
+
+ private static IHeadlessWindow GetImpl(this TopLevel topLevel)
+ {
+ return topLevel.PlatformImpl as IHeadlessWindow ??
+ throw new InvalidOperationException("TopLevel must be a headless window.");
+ }
+}
diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
similarity index 68%
rename from src/Avalonia.Headless/HeadlessWindowImpl.cs
rename to src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
index a801474f21..b15c1eb327 100644
--- a/src/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -17,20 +17,20 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
- class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
+ internal class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
{
- private IKeyboardDevice _keyboard;
- private Stopwatch _st = Stopwatch.StartNew();
- private Pointer _mousePointer;
- private WriteableBitmap _lastRenderedFrame;
- private object _sync = new object();
+ private readonly IKeyboardDevice _keyboard;
+ private readonly Stopwatch _st = Stopwatch.StartNew();
+ private readonly Pointer _mousePointer;
+ private WriteableBitmap? _lastRenderedFrame;
+ private readonly object _sync = new object();
public bool IsPopup { get; }
public HeadlessWindowImpl(bool isPopup)
{
IsPopup = isPopup;
Surfaces = new object[] { this };
- _keyboard = AvaloniaLocator.Current.GetService();
+ _keyboard = AvaloniaLocator.Current.GetRequiredService();
_mousePointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
MouseDevice = new MouseDevice(_mousePointer);
ClientSize = new Size(1024, 768);
@@ -48,13 +48,13 @@ namespace Avalonia.Headless
public double RenderScaling { get; } = 1;
public double DesktopScaling => RenderScaling;
public IEnumerable Surfaces { get; }
- public Action Input { get; set; }
- public Action Paint { get; set; }
- public Action Resized { get; set; }
- public Action ScalingChanged { get; set; }
+ public Action? Input { get; set; }
+ public Action? Paint { get; set; }
+ public Action? Resized { get; set; }
+ public Action? ScalingChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) =>
- new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces);
+ new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor!, () => Surfaces);
public void Invalidate(Rect rect)
{
@@ -65,18 +65,18 @@ namespace Avalonia.Headless
InputRoot = inputRoot;
}
- public IInputRoot InputRoot { get; set; }
+ public IInputRoot? InputRoot { get; set; }
public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling);
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
- public void SetCursor(ICursorImpl cursor)
+ public void SetCursor(ICursorImpl? cursor)
{
}
- public Action Closed { get; set; }
+ public Action? Closed { get; set; }
public IMouseDevice MouseDevice { get; }
public void Show(bool activate, bool isDialog)
@@ -101,17 +101,17 @@ namespace Avalonia.Headless
}
public PixelPoint Position { get; set; }
- public Action PositionChanged { get; set; }
+ public Action? PositionChanged { get; set; }
public void Activate()
{
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
- public Action Deactivated { get; set; }
- public Action Activated { get; set; }
+ public Action? Deactivated { get; set; }
+ public Action? Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280);
- public void Resize(Size clientSize, PlatformResizeReason reason)
+ public void Resize(Size clientSize, WindowResizeReason reason)
{
// Emulate X11 behavior here
if (IsPopup)
@@ -123,13 +123,13 @@ namespace Avalonia.Headless
});
}
- void DoResize(Size clientSize)
+ private void DoResize(Size clientSize)
{
// Uncomment this check and experience a weird bug in layout engine
if (ClientSize != clientSize)
{
ClientSize = clientSize;
- Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified);
+ Resized?.Invoke(clientSize, WindowResizeReason.Unspecified);
}
}
@@ -145,8 +145,8 @@ namespace Avalonia.Headless
public IScreenImpl Screen { get; } = new HeadlessScreensStub();
public WindowState WindowState { get; set; }
- public Action WindowStateChanged { get; set; }
- public void SetTitle(string title)
+ public Action? WindowStateChanged { get; set; }
+ public void SetTitle(string? title)
{
}
@@ -156,7 +156,7 @@ namespace Avalonia.Headless
}
- public void SetIcon(IWindowIconImpl icon)
+ public void SetIcon(IWindowIconImpl? icon)
{
}
@@ -171,9 +171,9 @@ namespace Avalonia.Headless
}
- public Func Closing { get; set; }
+ public Func? Closing { get; set; }
- class FramebufferProxy : ILockedFramebuffer
+ private class FramebufferProxy : ILockedFramebuffer
{
private readonly ILockedFramebuffer _fb;
private readonly Action _onDispose;
@@ -214,28 +214,37 @@ namespace Avalonia.Headless
});
}
- public IRef GetLastRenderedFrame()
+ public Bitmap? GetLastRenderedFrame()
{
lock (_sync)
- return _lastRenderedFrame?.PlatformImpl?.CloneAs();
+ {
+ if (_lastRenderedFrame is null)
+ {
+ return null;
+ }
+
+ using var lockedFramebuffer = _lastRenderedFrame.Lock();
+ return new Bitmap(lockedFramebuffer.Format, AlphaFormat.Opaque, lockedFramebuffer.Address,
+ lockedFramebuffer.Size, lockedFramebuffer.Dpi, lockedFramebuffer.RowBytes);
+ }
}
private ulong Timestamp => (ulong)_st.ElapsedMilliseconds;
// TODO: Hook recent Popup changes.
- IPopupPositioner IPopupImpl.PopupPositioner => null;
+ IPopupPositioner IPopupImpl.PopupPositioner => null!;
public Size MaxAutoSizeHint => new Size(1920, 1080);
- public Action TransparencyLevelChanged { get; set; }
+ public Action? TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
- public Action GotInputWhenDisabled { get; set; }
+ public Action? GotInputWhenDisabled { get; set; }
public bool IsClientAreaExtendedToDecorations => false;
- public Action ExtendClientAreaToDecorationsChanged { get; set; }
+ public Action? ExtendClientAreaToDecorationsChanged { get; set; }
public bool NeedsManagedDecorations => false;
@@ -243,17 +252,12 @@ namespace Avalonia.Headless
public Thickness OffScreenMargin => new Thickness();
- public Action LostFocus { get; set; }
+ public Action? LostFocus { get; set; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
- public object TryGetFeature(Type featureType)
+ public object? TryGetFeature(Type featureType)
{
- if (featureType == typeof(IStorageProvider))
- {
- return new NoopStorageProvider();
- }
-
- if(featureType == typeof(IClipboard))
+ if(featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService();
}
@@ -263,46 +267,58 @@ namespace Avalonia.Headless
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers)
{
- Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyDown, key, modifiers));
+ Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyDown, key, modifiers));
}
void IHeadlessWindow.KeyRelease(Key key, RawInputModifiers modifiers)
{
- Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyUp, key, modifiers));
+ Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyUp, key, modifiers));
}
- void IHeadlessWindow.MouseDown(Point point, int button, RawInputModifiers modifiers)
+ void IHeadlessWindow.MouseDown(Point point, MouseButton button, RawInputModifiers modifiers)
{
- Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
- button == 0 ? RawPointerEventType.LeftButtonDown :
- button == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.RightButtonDown,
- point, modifiers));
+ Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!,
+ button switch
+ {
+ MouseButton.Left => RawPointerEventType.LeftButtonDown,
+ MouseButton.Right => RawPointerEventType.RightButtonDown,
+ MouseButton.Middle => RawPointerEventType.MiddleButtonDown,
+ MouseButton.XButton1 => RawPointerEventType.XButton1Down,
+ MouseButton.XButton2 => RawPointerEventType.XButton2Down,
+ _ => RawPointerEventType.Move,
+ }, point, modifiers));
}
void IHeadlessWindow.MouseMove(Point point, RawInputModifiers modifiers)
{
- Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
+ Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!,
RawPointerEventType.Move, point, modifiers));
}
- void IHeadlessWindow.MouseUp(Point point, int button, RawInputModifiers modifiers)
+ void IHeadlessWindow.MouseUp(Point point, MouseButton button, RawInputModifiers modifiers)
{
- Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
- button == 0 ? RawPointerEventType.LeftButtonUp :
- button == 1 ? RawPointerEventType.MiddleButtonUp : RawPointerEventType.RightButtonUp,
- point, modifiers));
+ Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!,
+ button switch
+ {
+ MouseButton.Left => RawPointerEventType.LeftButtonUp,
+ MouseButton.Right => RawPointerEventType.RightButtonUp,
+ MouseButton.Middle => RawPointerEventType.MiddleButtonUp,
+ MouseButton.XButton1 => RawPointerEventType.XButton1Up,
+ MouseButton.XButton2 => RawPointerEventType.XButton2Up,
+ _ => RawPointerEventType.Move,
+ }, point, modifiers));
}
void IHeadlessWindow.MouseWheel(Point point, Vector delta, RawInputModifiers modifiers)
{
- Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, InputRoot,
+ Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, InputRoot!,
point, delta, modifiers));
}
void IHeadlessWindow.DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
{
var device = AvaloniaLocator.Current.GetRequiredService();
- Input?.Invoke(new RawDragEvent(device, type, InputRoot, point, data, effects, modifiers));
+ Input?.Invoke(new RawDragEvent(device, type, InputRoot!, point, data, effects, modifiers));
}
void IWindowImpl.Move(PixelPoint point)
@@ -310,7 +326,7 @@ namespace Avalonia.Headless
}
- public IPopupImpl CreatePopup()
+ public IPopupImpl? CreatePopup()
{
// TODO: Hook recent Popup changes.
return null;
diff --git a/src/Avalonia.Headless/IHeadlessWindow.cs b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs
similarity index 65%
rename from src/Avalonia.Headless/IHeadlessWindow.cs
rename to src/Headless/Avalonia.Headless/IHeadlessWindow.cs
index dfb3a4c433..f3da2335bc 100644
--- a/src/Avalonia.Headless/IHeadlessWindow.cs
+++ b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs
@@ -6,15 +6,15 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
- public interface IHeadlessWindow
+ internal interface IHeadlessWindow
{
- IRef GetLastRenderedFrame();
+ Bitmap? GetLastRenderedFrame();
void KeyPress(Key key, RawInputModifiers modifiers);
void KeyRelease(Key key, RawInputModifiers modifiers);
- void MouseDown(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
+ void MouseDown(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None);
- void MouseUp(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
+ void MouseUp(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseWheel(Point point, Vector delta, RawInputModifiers modifiers = RawInputModifiers.None);
- void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers);
+ void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None);
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index af4a70f128..ccc8cab8ae 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -65,7 +65,7 @@ using Avalonia.Rendering.Composition;
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Action TransparencyLevelChanged { get; set; }
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
new file mode 100644
index 0000000000..339b720d10
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.Reflection.Emit;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes
+{
+ class AvaloniaXamlIlArrayConstantAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
+ {
+ private readonly IXamlType _elementType;
+ private readonly IReadOnlyList _values;
+
+ public AvaloniaXamlIlArrayConstantAstNode(IXamlLineInfo lineInfo, IXamlType arrayType, IXamlType elementType, IReadOnlyList values) : base(lineInfo)
+ {
+ _elementType = elementType;
+ _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; }
+
+ public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ codeGen.Ldc_I4(_values.Count)
+ .Newarr(_elementType);
+
+ for (var index = 0; index < _values.Count; index++)
+ {
+ var value = _values[index];
+
+ codeGen
+ .Dup()
+ .Ldc_I4(index);
+
+ context.Emit(value, codeGen, _elementType);
+
+ if (value.Type.GetClrType() is { IsValueType: true } valTypeInObjArr)
+ {
+ if (!_elementType.IsValueType)
+ {
+ codeGen.Box(valTypeInObjArr);
+ }
+ // It seems like ASM codegen for "stelem valuetype" and "stelem.i4" is identical,
+ // so we don't need to try to optimize it here.
+ codeGen.Emit(OpCodes.Stelem, valTypeInObjArr);
+ }
+ else
+ {
+ codeGen.Stelem_ref();
+ }
+ }
+
+ return XamlILNodeEmitResult.Type(0, Type.GetClrType());
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 5ca2b09eba..23c67df810 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -58,7 +58,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
- new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
+ new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
+ new AvaloniaXamlIlThemeVariantProviderTransformer()
);
InsertBefore(
new AvaloniaXamlIlOptionMarkupExtensionTransformer());
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
index d8524cfd88..cd005ce24d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
@@ -198,6 +198,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
}
}
+
+ if (type.Equals(types.ColumnDefinition) || type.Equals(types.RowDefinition))
+ {
+ try
+ {
+ var gridLength = GridLength.Parse(text);
+
+ result = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength);
+
+ var definitionConstructorGridLength = type.GetConstructor(new List {types.GridLength});
+ var lengthNode = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength);
+ var definitionTypeRef = new XamlAstClrTypeReference(node, type, false);
+
+ result = new XamlAstNewClrObjectNode(node, definitionTypeRef,
+ definitionConstructorGridLength, new List {lengthNode});
+
+ return true;
+ }
+ catch
+ {
+ throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
+ }
+ }
if (type.Equals(types.Cursor))
{
@@ -211,16 +234,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
- if (type.Equals(types.ColumnDefinitions))
- {
- return ConvertDefinitionList(node, text, types, types.ColumnDefinitions, types.ColumnDefinition, "column definitions", out result);
- }
-
- if (type.Equals(types.RowDefinitions))
- {
- return ConvertDefinitionList(node, text, types, types.RowDefinitions, types.RowDefinition, "row definitions", out result);
- }
-
if (types.IBrush.IsAssignableFrom(type))
{
if (Color.TryParse(text, out Color color))
@@ -295,46 +308,89 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
- result = null;
- return false;
- }
-
- private static bool ConvertDefinitionList(
- IXamlAstValueNode node,
- string text,
- AvaloniaXamlIlWellKnownTypes types,
- IXamlType listType,
- IXamlType elementType,
- string errorDisplayName,
- out IXamlAstValueNode result)
- {
- try
+ // Keep it in the end, so more specific parsers can be applied.
+ var elementType = GetElementType(type, context.Configuration.WellKnownTypes);
+ if (elementType is not null)
{
- var lengths = GridLength.ParseLengths(text);
-
- var definitionTypeRef = new XamlAstClrTypeReference(node, elementType, false);
+ string[] items;
+ // Normalize special case of Points collection.
+ if (elementType == types.Point)
+ {
+ var pointParts = text.Split(new[] { ",", " " }, StringSplitOptions.RemoveEmptyEntries);
+ if (pointParts.Length % 2 == 0)
+ {
+ items = new string[pointParts.Length / 2];
+ for (int i = 0; i < pointParts.Length; i += 2)
+ {
+ items[i / 2] = string.Format(CultureInfo.InvariantCulture, "{0} {1}", pointParts[i],
+ pointParts[i + 1]);
+ }
+ }
+ else
+ {
+ throw new XamlX.XamlLoadException($"Invalid PointsList.", node);
+ }
+ }
+ else
+ {
+ const StringSplitOptions trimOption = (StringSplitOptions)2; // StringSplitOptions.TrimEntries
+ var separators = new[] { "," };
+ var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption;
- var definitionConstructorGridLength = elementType.GetConstructor(new List {types.GridLength});
+ items = text.Split(separators, splitOptions ^ trimOption);
+ // Compiler targets netstandard, so we need to emulate StringSplitOptions.TrimEntries, if it was requested.
+ if (splitOptions.HasFlag(trimOption))
+ {
+ items = items.Select(i => i.Trim()).ToArray();
+ }
+ }
- IXamlAstValueNode CreateDefinitionNode(GridLength length)
+ var nodes = new IXamlAstValueNode[items.Length];
+ for (var index = 0; index < items.Length; index++)
{
- var lengthNode = new AvaloniaXamlIlGridLengthAstNode(node, types, length);
+ var success = XamlTransformHelpers.TryGetCorrectlyTypedValue(
+ context,
+ new XamlAstTextNode(node, items[index], true, context.Configuration.WellKnownTypes.String),
+ elementType, out var itemNode);
+ if (!success)
+ {
+ result = null;
+ return false;
+ }
- return new XamlAstNewClrObjectNode(node, definitionTypeRef,
- definitionConstructorGridLength, new List {lengthNode});
+ nodes[index] = itemNode;
}
- var definitionNodes =
- new List(lengths.Select(CreateDefinitionNode));
-
- result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, listType, elementType, definitionNodes);
+ if (types.AvaloniaList.MakeGenericType(elementType).IsAssignableFrom(type))
+ {
+ result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, elementType, nodes);
+ return true;
+ }
+ else if (type.IsArray)
+ {
+ result = new AvaloniaXamlIlArrayConstantAstNode(node, elementType.MakeArrayType(1), elementType, nodes);
+ return true;
+ }
+ else if (type == context.Configuration.WellKnownTypes.IListOfT.MakeGenericType(elementType))
+ {
+ var listType = context.Configuration.WellKnownTypes.IListOfT.MakeGenericType(elementType);
+ result = new AvaloniaXamlIlArrayConstantAstNode(node, listType, elementType, nodes);
+ return true;
+ }
- return true;
- }
- catch
- {
- throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a {errorDisplayName}", node);
+ result = null;
+ return false;
}
+
+ result = null;
+ return false;
+ }
+
+ private static IXamlType GetElementType(IXamlType type, XamlTypeWellKnownTypes types)
+ {
+ return type.GetAllInterfaces().FirstOrDefault(i =>
+ i.FullName.StartsWith(types.IEnumerableT.FullName))?
+ .GenericArguments[0];
}
}
}
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 db8d604154..8e04a7d467 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
@@ -24,7 +24,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
var mergeSourceNodes = new List();
- var hasAnyNonMergedResource = false;
+ var mergedResourceWasAdded = false;
foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
{
void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
@@ -38,7 +38,8 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
&& objectInitialization.Manipulation is XamlPropertyAssignmentNode sourceAssignmentNode)
{
parent.Children.Remove(assignmentNode);
- mergeSourceNodes.Add(sourceAssignmentNode);
+ mergeSourceNodes.Add(sourceAssignmentNode);
+ mergedResourceWasAdded = true;
}
else
{
@@ -47,15 +48,10 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
valueNode);
}
}
- else
- {
- hasAnyNonMergedResource = true;
- }
-
- if (hasAnyNonMergedResource && mergeSourceNodes.Any())
+ else if (mergeSourceNodes.Any())
{
throw new XamlDocumentParseException(context.CurrentDocument,
- "Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed",
+ "MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.",
valueNode);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs
new file mode 100644
index 0000000000..05df8be1b6
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+internal class AvaloniaXamlIlThemeVariantProviderTransformer : IXamlAstTransformer
+{
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ var type = context.GetAvaloniaTypes().IThemeVariantProvider;
+ if (!(node is XamlAstObjectNode on
+ && type.IsAssignableFrom(on.Type.GetClrType())))
+ return node;
+
+ var keyDirective = on.Children.FirstOrDefault(n => n is XamlAstXmlDirective d
+ && d.Namespace == XamlNamespaces.Xaml2006 &&
+ d.Name == "Key") as XamlAstXmlDirective;
+ if (keyDirective is null)
+ return node;
+
+ var keyProp = type.Properties.First(p => p.Name == "Key");
+ on.Children.Add(new XamlAstXamlPropertyValueNode(keyDirective,
+ new XamlAstClrProperty(keyDirective, keyProp, context.Configuration),
+ keyDirective.Values, true));
+
+ return node;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 60a7d953ab..8ab84f4615 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -33,6 +33,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType InheritDataTypeFromItemsAttribute { get; }
public IXamlType MarkupExtensionOptionAttribute { get; }
public IXamlType MarkupExtensionDefaultOptionAttribute { get; }
+ public IXamlType AvaloniaList { get; }
public IXamlType OnExtensionType { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
@@ -109,6 +110,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
+ public IXamlType IThemeVariantProvider { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
public IXamlType Style { get; }
@@ -141,6 +143,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
InheritDataTypeFromItemsAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromItemsAttribute");
MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute");
MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute");
+ AvaloniaList = cfg.TypeSystem.GetType("Avalonia.Collections.AvaloniaList`1");
OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, AvaloniaObject,
AvaloniaProperty,
@@ -248,6 +251,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
+ IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind });
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
index 5dd0b042e1..5d1025f30d 160000
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
@@ -1 +1 @@
-Subproject commit 5dd0b042e144e677638224c49fec16dab66143e8
+Subproject commit 5d1025f30d0ed6d8f419d82959c148276301f393
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
index e1b594e331..7f52c872ed 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
+using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
@@ -10,6 +11,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
private object? _anchor;
private BindingPriority _priority;
+ private ThemeVariant? _currentThemeVariant;
public DynamicResourceExtension()
{
@@ -36,6 +38,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
(object?)serviceProvider.GetFirstParent();
}
+ _currentThemeVariant = StaticResourceExtension.GetDictionaryVariant(serviceProvider);
+
return this;
}
@@ -59,7 +63,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
else if (_anchor is IResourceProvider resourceProvider)
{
- var source = resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty));
+ var source = resourceProvider.GetResourceObservable(ResourceKey, _currentThemeVariant, GetConverter(targetProperty));
return InstancedBinding.OneWay(source, _priority);
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
index 3de669b1e4..c23c31e24c 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
@@ -33,7 +33,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var provideTarget = serviceProvider.GetService();
var targetObject = provideTarget?.TargetObject;
var targetProperty = provideTarget?.TargetProperty;
- var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant;
+ var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant
+ ?? GetDictionaryVariant(serviceProvider);
var targetType = targetProperty switch
{
@@ -78,6 +79,25 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
return ColorToBrushConverter.Convert(control.FindResource(ResourceKey!), targetType);
}
+
+ internal static ThemeVariant? GetDictionaryVariant(IServiceProvider serviceProvider)
+ {
+ var parents = serviceProvider.GetService()?.Parents;
+ if (parents is null)
+ {
+ return null;
+ }
+
+ foreach (var parent in parents)
+ {
+ if (parent is IThemeVariantProvider { Key: { } setKey })
+ {
+ return setKey;
+ }
+ }
+
+ return null;
+ }
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
index fbcfdde565..eee02ea0d8 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// When used in runtime, this type might be unsafe with trimming and AOT.
///
[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)]
- public class ResourceInclude : IResourceProvider
+ public class ResourceInclude : IResourceProvider, IThemeVariantProvider
{
private readonly IServiceProvider? _serviceProvider;
private readonly Uri? _baseUri;
@@ -65,6 +65,8 @@ namespace Avalonia.Markup.Xaml.Styling
///
public Uri? Source { get; set; }
+ ThemeVariant? IThemeVariantProvider.Key { get; set; }
+
bool IResourceNode.HasResources => Loaded.HasResources;
public event EventHandler? OwnerChanged
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
index 4bbdda31d8..b94eccf7c0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Metadata;
@@ -13,6 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
public Type? TargetType { get; set; }
- public ControlTemplateResult? Build(TemplatedControl control) => TemplateContent.Load(Content);
+ public TemplateResult? Build(TemplatedControl control) => TemplateContent.Load(Content);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index 89b0468c6e..b45898d8bd 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.Templates
public Control? Build(object? data, Control? existing)
{
- return existing ?? TemplateContent.Load(Content)?.Control;
+ return existing ?? TemplateContent.Load(Content)?.Result;
}
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
index c228a58990..f31a693e72 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object? Content { get; set; }
- public Panel? Build() => (Panel?)TemplateContent.Load(Content)?.Control;
+ public Panel? Build() => (Panel?)TemplateContent.Load(Content)?.Result;
object? ITemplate.Build() => Build();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
index 62febebc8c..5999a8021e 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object? Content { get; set; }
- public Control? Build() => TemplateContent.Load(Content)?.Control;
+ public Control? Build() => TemplateContent.Load(Content)?.Result;
object? ITemplate.Build() => Build();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
index 08e897c514..504478f9b3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
@@ -1,15 +1,16 @@
using System;
+using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml.Templates
{
public static class TemplateContent
{
- public static ControlTemplateResult? Load(object? templateContent)
+ public static TemplateResult? Load(object? templateContent)
{
if (templateContent is Func direct)
{
- return (ControlTemplateResult?)direct(null);
+ return (TemplateResult?)direct(null);
}
if (templateContent is null)
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index a5b308523f..98c3b61c9f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.Templates
public Control? Build(object? data)
{
- var visualTreeForItem = TemplateContent.Load(Content)?.Control;
+ var visualTreeForItem = TemplateContent.Load(Content)?.Result;
if (visualTreeForItem != null)
{
visualTreeForItem.DataContext = data;
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
index ba96ac15b3..0cc7cc5468 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
scope.Complete();
if(typeof(T) == typeof(Control))
- return new ControlTemplateResult((Control)obj, scope);
+ return new TemplateResult((Control)obj, scope);
return new TemplateResult((T)obj, scope);
};
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 13eae1992c..24fd7e3933 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -108,7 +108,7 @@ namespace Avalonia.Win32.Interop.Wpf
if (_finalSize == _previousSize)
return finalSize;
_previousSize = _finalSize;
- _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize(), PlatformResizeReason.Unspecified);
+ _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize(), WindowResizeReason.Unspecified);
return base.ArrangeOverride(finalSize);
}
@@ -229,7 +229,7 @@ namespace Avalonia.Win32.Interop.Wpf
Action ITopLevelImpl.Input { get; set; } //TODO
Action ITopLevelImpl.Paint { get; set; }
- Action ITopLevelImpl.Resized { get; set; }
+ Action ITopLevelImpl.Resized { get; set; }
Action ITopLevelImpl.ScalingChanged { get; set; }
Action ITopLevelImpl.TransparencyLevelChanged { get; set; }
diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
index db650db4b0..aabf361844 100644
--- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
+++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
@@ -85,16 +85,18 @@ namespace Avalonia.Win32.Input
_parent = parent;
- var langId= PRIMARYLANGID(LGID(HKL));
+ var langId = PRIMARYLANGID(LGID(HKL));
- if(langId != _langId)
+ if (IsActive)
{
- DisableImm();
+ if (langId != _langId)
+ {
+ DisableImm();
+ EnableImm();
+ }
}
_langId = langId;
-
- EnableImm();
}
public void ClearLanguageAndWindow()
diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs
index 75c1a2d564..1470435134 100644
--- a/src/Windows/Avalonia.Win32/PopupImpl.cs
+++ b/src/Windows/Avalonia.Win32/PopupImpl.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Controls;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
@@ -135,7 +136,7 @@ namespace Avalonia.Win32
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Move(position);
- Resize(size, PlatformResizeReason.Layout);
+ Resize(size, WindowResizeReason.Layout);
//TODO: We ignore the scaling override for now
}
diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs
index 05d9faa97b..8f9fc5fa80 100644
--- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs
+++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs
@@ -212,7 +212,7 @@ namespace Avalonia.Win32
if (PlatformImpl is { } platformImpl)
{
platformImpl.Move(position);
- platformImpl.Resize(size, PlatformResizeReason.Layout);
+ platformImpl.Resize(size, WindowResizeReason.Layout);
}
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 0cb8b09579..2a3255bb70 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -133,7 +133,7 @@ namespace Avalonia.Win32
_scaling = dpi / 96.0;
ScalingChanged?.Invoke(_scaling);
- using (SetResizeReason(PlatformResizeReason.DpiChange))
+ using (SetResizeReason(WindowResizeReason.DpiChange))
{
SetWindowPos(hWnd,
IntPtr.Zero,
@@ -611,7 +611,7 @@ namespace Avalonia.Win32
case WindowsMessage.WM_ENTERSIZEMOVE:
- _resizeReason = PlatformResizeReason.User;
+ _resizeReason = WindowResizeReason.User;
break;
case WindowsMessage.WM_SIZE:
@@ -658,7 +658,7 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_EXITSIZEMOVE:
- _resizeReason = PlatformResizeReason.Unspecified;
+ _resizeReason = WindowResizeReason.Unspecified;
break;
case WindowsMessage.WM_MOVE:
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 545513c732..9217f42952 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -59,7 +59,7 @@ namespace Avalonia.Win32
private double _extendTitleBarHint = -1;
private readonly bool _isUsingComposition;
private readonly IBlurHost? _blurHost;
- private PlatformResizeReason _resizeReason;
+ private WindowResizeReason _resizeReason;
private MOUSEMOVEPOINT _lastWmMousePoint;
#if USE_MANAGED_DRAG
@@ -200,7 +200,7 @@ namespace Avalonia.Win32
public Action? Paint { get; set; }
- public Action? Resized { get; set; }
+ public Action? Resized { get; set; }
public Action? ScalingChanged { get; set; }
@@ -588,7 +588,7 @@ namespace Avalonia.Win32
public IRenderer CreateRenderer(IRenderRoot root) =>
new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces);
- public void Resize(Size value, PlatformResizeReason reason)
+ public void Resize(Size value, WindowResizeReason reason)
{
if (WindowState != WindowState.Normal)
return;
@@ -1053,7 +1053,7 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();
- Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout);
+ Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), WindowResizeReason.Layout);
unsafe
{
@@ -1462,7 +1462,7 @@ namespace Avalonia.Win32
///
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0);
- private ResizeReasonScope SetResizeReason(PlatformResizeReason reason)
+ private ResizeReasonScope SetResizeReason(WindowResizeReason reason)
{
var old = _resizeReason;
_resizeReason = reason;
@@ -1487,9 +1487,9 @@ namespace Avalonia.Win32
private struct ResizeReasonScope : IDisposable
{
private readonly WindowImpl _owner;
- private readonly PlatformResizeReason _restore;
+ private readonly WindowResizeReason _restore;
- public ResizeReasonScope(WindowImpl owner, PlatformResizeReason restore)
+ public ResizeReasonScope(WindowImpl owner, WindowResizeReason restore)
{
_owner = owner;
_restore = restore;
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index ec6ea56d9d..6ca0cf7ace 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -150,7 +150,7 @@ namespace Avalonia.iOS
public IEnumerable Surfaces { get; set; }
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Action TransparencyLevelChanged { get; set; }
public Action Closed { get; set; }
@@ -225,7 +225,7 @@ namespace Avalonia.iOS
public override void LayoutSubviews()
{
- _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize, PlatformResizeReason.Layout);
+ _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize, WindowResizeReason.Layout);
base.LayoutSubviews();
}
diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
index 98d2807db5..9f13520086 100644
--- a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
@@ -457,5 +457,39 @@ namespace Avalonia.Base.UnitTests.Layout
Assert.Equal(1, layoutCount);
}
+
+ [Fact]
+ public void Child_Can_Invalidate_Parent_Measure_During_Arrange()
+ {
+ // Issue #11015.
+ //
+ // - Child invalidates parent measure in arrange pass
+ // - Parent is added to measure & arrange queues
+ // - Arrange pass dequeues parent
+ // - Measure is not valid so parent is not arranged
+ // - Parent is measured
+ // - Parent has been dequeued from arrange queue so no arrange is performed
+ var child = new LayoutTestControl();
+ var parent = new LayoutTestControl { Child = child };
+ var root = new LayoutTestRoot { Child = parent };
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ child.DoArrangeOverride = (_, s) =>
+ {
+ parent.InvalidateMeasure();
+ return s;
+ };
+
+ child.InvalidateMeasure();
+ parent.InvalidateMeasure();
+
+ root.LayoutManager.ExecuteLayoutPass();
+
+ Assert.True(child.IsMeasureValid);
+ Assert.True(child.IsArrangeValid);
+ Assert.True(parent.IsMeasureValid);
+ Assert.True(parent.IsArrangeValid);
+ }
}
}
diff --git a/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
index e9b82d5381..8eadb3a3f0 100644
--- a/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
+++ b/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
@@ -1,7 +1,9 @@
using System;
+using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Styling;
+using Avalonia.Threading;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
using Moq;
@@ -30,27 +32,23 @@ namespace Avalonia.Benchmarks.Themes
_app.Dispose();
}
- [Benchmark]
- public void RepeatButton()
+ [Benchmark()]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void CreateButton()
{
- var button = new RepeatButton();
+ var button = new Button();
_root.Child = button;
_root.LayoutManager.ExecuteLayoutPass();
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
}
private static IDisposable CreateApp()
{
var services = new TestServices(
- assetLoader: new AssetLoader(),
- globalClock: new MockGlobalClock(),
- platform: new AppBuilder().RuntimePlatform,
- renderInterface: new MockPlatformRenderInterface(),
- standardCursorFactory: Mock.Of(),
- theme: () => LoadFluentTheme(),
+ renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform(),
- fontManagerImpl: new MockFontManagerImpl(),
- textShaperImpl: new MockTextShaperImpl(),
- windowingPlatform: new MockWindowingPlatform());
+ standardCursorFactory: new NullCursorFactory(),
+ theme: () => LoadFluentTheme());
return UnitTestApplication.Start(services);
}
diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
index 7c0a3f8bdf..ac174e4bc2 100644
--- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
+++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
@@ -1,5 +1,5 @@
using System;
-
+using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
@@ -29,6 +29,7 @@ namespace Avalonia.Benchmarks.Themes
}
[Benchmark]
+ [MethodImpl(MethodImplOptions.NoInlining)]
public bool InitFluentTheme()
{
UnitTestApplication.Current.Styles[0] = new FluentTheme();
@@ -36,6 +37,7 @@ namespace Avalonia.Benchmarks.Themes
}
[Benchmark]
+ [MethodImpl(MethodImplOptions.NoInlining)]
public bool InitSimpleTheme()
{
UnitTestApplication.Current.Styles[0] = new SimpleTheme();
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 34311949ef..765f2d1c19 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -830,7 +830,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
};
}
- window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified);
+ window.PlatformImpl?.Resize(new Size(700D, 500D), WindowResizeReason.Unspecified);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
Assert.True(raised);
}
@@ -886,7 +886,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
};
}
- window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified);
+ window.PlatformImpl?.Resize(new Size(700D, 500D), WindowResizeReason.Unspecified);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
Assert.False(raised);
}
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index 62b5d889a8..0884dd306a 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -142,7 +142,7 @@ namespace Avalonia.Controls.UnitTests
// The user has resized the window, so we can no longer auto-size.
var target = new TestTopLevel(impl.Object);
- impl.Object.Resized(new Size(100, 200), PlatformResizeReason.Unspecified);
+ impl.Object.Resized(new Size(100, 200), WindowResizeReason.Unspecified);
Assert.Equal(100, target.Width);
Assert.Equal(200, target.Height);
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index cada2bfa6f..b59f6e03f7 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -704,8 +704,8 @@ namespace Avalonia.Controls.UnitTests
var clientSize = new Size(200, 200);
var maxClientSize = new Size(480, 480);
- windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny()))
- .Callback((size, reason) =>
+ windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny()))
+ .Callback((size, reason) =>
{
clientSize = size.Constrain(maxClientSize);
windowImpl.Object.Resized?.Invoke(clientSize, reason);
@@ -853,7 +853,7 @@ namespace Avalonia.Controls.UnitTests
target.PlatformImpl.ScalingChanged(1.5);
target.PlatformImpl.Resized(
new Size(210.66666666666666, 118.66666666666667),
- PlatformResizeReason.DpiChange);
+ WindowResizeReason.DpiChange);
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
@@ -911,7 +911,7 @@ namespace Avalonia.Controls.UnitTests
target.LayoutManager.ExecuteLayoutPass();
var windowImpl = Mock.Get(target.PlatformImpl);
- windowImpl.Verify(x => x.Resize(new Size(410, 800), PlatformResizeReason.Application));
+ windowImpl.Verify(x => x.Resize(new Size(410, 800), WindowResizeReason.Application));
Assert.Equal(410, target.Width);
}
}
@@ -936,7 +936,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
- target.PlatformImpl.Resized(new Size(410, 800), PlatformResizeReason.User);
+ target.PlatformImpl.Resized(new Size(410, 800), WindowResizeReason.User);
Assert.Equal(410, target.Width);
Assert.Equal(800, target.Height);
@@ -963,7 +963,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
- target.PlatformImpl.Resized(new Size(400, 810), PlatformResizeReason.User);
+ target.PlatformImpl.Resized(new Size(400, 810), WindowResizeReason.User);
Assert.Equal(400, target.Width);
Assert.Equal(810, target.Height);
@@ -991,7 +991,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
- target.PlatformImpl.Resized(new Size(410, 810), PlatformResizeReason.Unspecified);
+ target.PlatformImpl.Resized(new Size(410, 810), WindowResizeReason.Unspecified);
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
diff --git a/tests/Avalonia.Headless.UnitTests/Avalonia.Headless.UnitTests.csproj b/tests/Avalonia.Headless.UnitTests/Avalonia.Headless.UnitTests.csproj
new file mode 100644
index 0000000000..78a3ab186e
--- /dev/null
+++ b/tests/Avalonia.Headless.UnitTests/Avalonia.Headless.UnitTests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net6.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Avalonia.Headless.UnitTests/InputTests.cs b/tests/Avalonia.Headless.UnitTests/InputTests.cs
new file mode 100644
index 0000000000..3c0ecbfdb7
--- /dev/null
+++ b/tests/Avalonia.Headless.UnitTests/InputTests.cs
@@ -0,0 +1,36 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Layout;
+using Avalonia.Threading;
+using Xunit;
+
+namespace Avalonia.Headless.UnitTests;
+
+public class InputTests
+{
+ [Fact]
+ public void Should_Click_Button_On_Window()
+ {
+ var buttonClicked = false;
+ var button = new Button
+ {
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch
+ };
+
+ button.Click += (_, _) => buttonClicked = true;
+
+ var window = new Window
+ {
+ Width = 100,
+ Height = 100,
+ Content = button
+ };
+ window.Show();
+
+ window.MouseDown(new Point(50, 50), MouseButton.Left);
+ window.MouseUp(new Point(50, 50), MouseButton.Left);
+
+ Assert.True(buttonClicked);
+ }
+}
diff --git a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs
new file mode 100644
index 0000000000..bc50686235
--- /dev/null
+++ b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs
@@ -0,0 +1,33 @@
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.Threading;
+using Xunit;
+
+namespace Avalonia.Headless.UnitTests;
+
+public class RenderingTests
+{
+ [Fact]
+ public void Should_Render_Last_Frame_To_Bitmap()
+ {
+ var window = new Window
+ {
+ Content = new ContentControl
+ {
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ Padding = new Thickness(4),
+ Content = new PathIcon
+ {
+ Data = StreamGeometry.Parse("M0,9 L10,0 20,9 19,10 10,2 1,10 z")
+ }
+ },
+ SizeToContent = SizeToContent.WidthAndHeight
+ };
+ window.Show();
+
+ var frame = window.CaptureRenderedFrame();
+ Assert.NotNull(frame);
+ }
+}
diff --git a/tests/Avalonia.Headless.UnitTests/TestApplication.cs b/tests/Avalonia.Headless.UnitTests/TestApplication.cs
new file mode 100644
index 0000000000..7bfa0144f3
--- /dev/null
+++ b/tests/Avalonia.Headless.UnitTests/TestApplication.cs
@@ -0,0 +1,24 @@
+using Avalonia.Headless.UnitTests;
+using Avalonia.Headless.XUnit;
+using Avalonia.Themes.Simple;
+using Xunit;
+
+[assembly: AvaloniaTestFramework(typeof(TestApplication))]
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
+
+namespace Avalonia.Headless.UnitTests;
+
+public class TestApplication : Application
+{
+ public TestApplication()
+ {
+ Styles.Add(new SimpleTheme());
+ }
+
+ public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure()
+ .UseSkia()
+ .UseHeadless(new AvaloniaHeadlessPlatformOptions
+ {
+ UseHeadlessDrawing = false
+ });
+}
diff --git a/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs b/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
new file mode 100644
index 0000000000..419ee5519e
--- /dev/null
+++ b/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Threading;
+using Xunit;
+
+namespace Avalonia.Headless.UnitTests;
+
+public class ThreadingTests
+{
+ [Fact]
+ public void Should_Be_On_Dispatcher_Thread()
+ {
+ Dispatcher.UIThread.VerifyAccess();
+ }
+
+ [Fact]
+ public async Task DispatcherTimer_Works_On_The_Same_Thread()
+ {
+ var currentThread = Thread.CurrentThread;
+ var tcs = new TaskCompletionSource();
+
+ DispatcherTimer.RunOnce(() =>
+ {
+ Assert.Equal(currentThread, Thread.CurrentThread);
+
+ tcs.SetResult();
+ }, TimeSpan.FromTicks(1));
+
+ await tcs.Task;
+ }
+}
diff --git a/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs b/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs
index e9d0a5d3a4..9d5df2fb46 100644
--- a/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs
@@ -13,7 +13,7 @@ namespace Avalonia.IntegrationTests.Appium
_session = fixture.Session;
var tabs = _session.FindElementByAccessibilityId("MainTabs");
- var tab = tabs.FindElementByName("ScrollBarTab");
+ var tab = tabs.FindElementByName("ScrollBar");
tab.Click();
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
index 9371a49ade..fa83ee199c 100644
--- a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Interactions;
using Xunit;
@@ -15,21 +16,95 @@ namespace Avalonia.IntegrationTests.Appium
_session = fixture.Session;
var tabs = _session.FindElementByAccessibilityId("MainTabs");
- var tab = tabs.FindElementByName("SliderTab");
+ var tab = tabs.FindElementByName("Slider");
tab.Click();
+
+ var reset = _session.FindElementByAccessibilityId("ResetSliders");
+ reset.Click();
+ }
+
+ [Fact]
+ public void Horizontal_Changes_Value_Dragging_Thumb_Right()
+ {
+ var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+ var thumb = slider.FindElementByAccessibilityId("thumb");
+ var initialThumbRect = thumb.Rect;
+
+ new Actions(_session).ClickAndHold(thumb).MoveByOffset(100, 0).Release().Perform();
+
+ var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
+ var boundValue = double.Parse(
+ _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+ CultureInfo.InvariantCulture);
+
+ Assert.True(value > 50);
+ Assert.Equal(value, boundValue);
+
+ var currentThumbRect = thumb.Rect;
+ Assert.True(currentThumbRect.Left > initialThumbRect.Left);
+ }
+
+ [Fact]
+ public void Horizontal_Changes_Value_Dragging_Thumb_Left()
+ {
+ var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+ var thumb = slider.FindElementByAccessibilityId("thumb");
+ var initialThumbRect = thumb.Rect;
+
+ new Actions(_session).ClickAndHold(thumb).MoveByOffset(-100, 0).Release().Perform();
+
+ var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
+ var boundValue = double.Parse(
+ _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+ CultureInfo.InvariantCulture);
+
+ Assert.True(value < 50);
+ Assert.Equal(value, boundValue);
+
+ var currentThumbRect = thumb.Rect;
+ Assert.True(currentThumbRect.Left < initialThumbRect.Left);
}
[Fact]
- public void Changes_Value_When_Clicking_Increase_Button()
+ public void Horizontal_Changes_Value_When_Clicking_Increase_Button()
{
- var slider = _session.FindElementByAccessibilityId("Slider");
+ var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+ var thumb = slider.FindElementByAccessibilityId("thumb");
+ var initialThumbRect = thumb.Rect;
+
+ new Actions(_session).MoveToElement(slider, 100, 0, MoveToElementOffsetOrigin.Center).Click().Perform();
+
+ var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
+ var boundValue = double.Parse(
+ _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+ CultureInfo.InvariantCulture);
+
+ Assert.True(value > 50);
+ Assert.Equal(value, boundValue);
+
+ var currentThumbRect = thumb.Rect;
+ Assert.True(currentThumbRect.Left > initialThumbRect.Left);
+ }
+
+ [Fact]
+ public void Horizontal_Changes_Value_When_Clicking_Decrease_Button()
+ {
+ var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+ var thumb = slider.FindElementByAccessibilityId("thumb");
+ var initialThumbRect = thumb.Rect;
+
+ new Actions(_session).MoveToElement(slider, -100, 0, MoveToElementOffsetOrigin.Center).Click().Perform();
- // slider.Text gets the Slider value
- Assert.True(double.Parse(slider.Text) == 30);
+ var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
+ var boundValue = double.Parse(
+ _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+ CultureInfo.InvariantCulture);
- new Actions(_session).Click(slider).Perform();
+ Assert.True(value < 50);
+ Assert.Equal(value, boundValue);
- Assert.Equal(50, Math.Round(double.Parse(slider.Text)));
+ var currentThumbRect = thumb.Rect;
+ Assert.True(currentThumbRect.Left < initialThumbRect.Left);
}
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 57d6a8902a..9f0b84733d 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -1978,7 +1978,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public bool Match(object data) => FancyDataType?.IsInstanceOfType(data) ?? true;
- public Control Build(object data) => TemplateContent.Load(Content)?.Control;
+ public Control Build(object data) => TemplateContent.Load(Content)?.Result;
}
public class CustomDataTemplateInherit : CustomDataTemplate { }
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
index 5e30198d00..421ed2c979 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
@@ -605,7 +605,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var control = new ContentControl();
- var result = (ContentPresenter)template.Build(control).Control;
+ var result = (ContentPresenter)template.Build(control).Result;
Assert.NotNull(result);
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs
index 0a45814efe..4404564733 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs
@@ -258,7 +258,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
var template = AvaloniaRuntimeXamlLoader.Parse(xaml);
- var parent = (ContentControl)template.Build(new ContentControl()).Control;
+ var parent = (ContentControl)template.Build(new ContentControl()).Result;
Assert.Equal("parent", parent.Name);
@@ -283,7 +283,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(typeof(ContentControl), template.TargetType);
- Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Control);
+ Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Result);
}
[Fact]
@@ -299,7 +299,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
var template = AvaloniaRuntimeXamlLoader.Parse(xaml);
- var panel = (Panel)template.Build(new ContentControl()).Control;
+ var panel = (Panel)template.Build(new ContentControl()).Result;
Assert.Equal(2, panel.Children.Count);
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
index aa76756069..d6f554cdfe 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
@@ -83,6 +83,34 @@ public class MergeResourceIncludeTests
Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents));
}
+ [Fact]
+ public void MergeResourceInclude_Is_Allowed_After_ResourceInclude()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @"
+
+ Red
+"),
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @"
+
+ Blue
+"),
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+
+
+")
+ };
+
+ AvaloniaRuntimeXamlLoader.LoadGroup(documents);
+ }
+
[Fact]
public void MergeResourceInclude_Works_With_Multiple_Resources()
{
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs
index 3ac4677694..2def84bb18 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs
@@ -1,9 +1,12 @@
-using System.Linq;
+using System;
+using System.Linq;
using Avalonia.Controls;
+using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
+using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
@@ -140,7 +143,7 @@ public class ThemeDictionariesTests : XamlTestBase
Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color);
}
- [Fact(Skip = "Not implemented")]
+ [Fact]
public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key()
{
var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@"
@@ -183,6 +186,135 @@ public class ThemeDictionariesTests : XamlTestBase
Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color);
}
+
+ [Fact]
+ public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key_From_Inner_File()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Inner.xaml"), @"
+
+
+"),
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+ Green
+
+
+
+
+
+ White
+
+
+
+
+
+")
+ };
+
+ var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
+ var dictionary = (ResourceDictionary)parsed[1]!;
+
+ dictionary.TryGetResource("InnerKey", ThemeVariant.Dark, out var resource);
+ var colorResource = Assert.IsType(resource);
+ Assert.Equal(Colors.White, colorResource);
+
+ dictionary.TryGetResource("InnerKey", ThemeVariant.Light, out resource);
+ colorResource = Assert.IsType(resource);
+ Assert.Equal(Colors.Green, colorResource);
+ }
+
+ [Fact]
+ public void DynamicResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key_From_Inner_File()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Inner.xaml"), @"
+
+
+"),
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+ Green
+
+
+
+
+
+ White
+
+
+
+
+
+")
+ };
+
+ var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
+ var dictionary1 = (ResourceDictionary)parsed[0]!;
+ var dictionary2 = (ResourceDictionary)parsed[1]!;
+ var ownerApp = new Application(); // DynamicResource needs an owner to work
+ ownerApp.RequestedThemeVariant = new ThemeVariant("FakeOne", null);
+ ownerApp.Resources.MergedDictionaries.Add(dictionary1);
+ ownerApp.Resources.MergedDictionaries.Add(dictionary2);
+
+ dictionary2.TryGetResource("InnerKey", ThemeVariant.Dark, out var resource);
+ var colorResource = Assert.IsAssignableFrom(resource);
+ Assert.Equal(Colors.White, colorResource.Color);
+
+ dictionary2.TryGetResource("InnerKey", ThemeVariant.Light, out resource);
+ colorResource = Assert.IsAssignableFrom(resource);
+ Assert.Equal(Colors.Green, colorResource.Color);
+ }
+
+ [Fact]
+ public void DynamicResource_Inside_Control_Inside_Of_ThemeDictionaries_Should_Use_Control_Theme_Variant()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+ Green
+
+
+
+
+
+ White
+
+
+
+
+
+")
+ };
+
+ var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
+ var dictionary = (ResourceDictionary)parsed[0]!;
+
+ dictionary.TryGetResource("Template", ThemeVariant.Dark, out var resource);
+ var control = Assert.IsType((resource as Template)?.Build());
+ control.Resources.MergedDictionaries.Add(dictionary);
+ Assert.Equal(Colors.Green, ((ISolidColorBrush)control[TextElement.ForegroundProperty]!).Color);
+ control.Resources.MergedDictionaries.Remove(dictionary);
+
+ dictionary.TryGetResource("Template", ThemeVariant.Light, out resource);
+ control = Assert.IsType((resource as Template)?.Build());
+ control.Resources.MergedDictionaries.Add(dictionary);
+ Assert.Equal(Colors.White, ((ISolidColorBrush)control[TextElement.ForegroundProperty]!).Color);
+ }
[Fact]
public void StaticResource_Outside_Of_Dictionaries_Should_Use_Control_ThemeVariant()
diff --git a/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs b/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs
index 3ac884df7d..b918f7180a 100644
--- a/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs
+++ b/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
Stroke = Brushes.DarkBlue,
Stretch = Stretch.Uniform,
Fill = Brushes.Violet,
- Points = new [] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) },
+ Points = new Points { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) },
StrokeThickness = 1
}
};
@@ -52,7 +52,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
Stroke = Brushes.DarkBlue,
Stretch = Stretch.Fill,
Fill = Brushes.Violet,
- Points = new[] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) },
+ Points = new Points { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) },
StrokeThickness = 5,
}
};
diff --git a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs
index d02d494ff2..12420b524a 100644
--- a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs
+++ b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
[Fact]
public async Task Polyline_1px_Stroke()
{
- var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
+ var polylinePoints = new Points { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
new Point(9, 1), new Point(10, 0), new Point(15, 0) };
Decorator target = new Decorator
@@ -44,7 +44,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
[Fact]
public async Task Polyline_10px_Stroke_PenLineJoin()
{
- var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
+ var polylinePoints = new Points { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
new Point(9, 1), new Point(10, 0), new Point(15, 0) };
Decorator target = new Decorator
diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs
index 5ef09a4d0f..de7cbc873c 100644
--- a/tests/Avalonia.UnitTests/CompositorTestServices.cs
+++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs
@@ -152,7 +152,7 @@ public class CompositorTestServices : IDisposable
public IEnumerable Surfaces { get; } = new[] { new DummyFramebufferSurface() };
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Action TransparencyLevelChanged { get; set; }
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index 142a9cd8ee..ca71a97a6e 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives.PopupPositioning;
using Moq;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.Controls;
namespace Avalonia.UnitTests
{
@@ -54,8 +55,8 @@ namespace Avalonia.UnitTests
windowImpl.Object.PositionChanged?.Invoke(x);
});
- windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny()))
- .Callback((x, y) =>
+ windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny()))
+ .Callback((x, y) =>
{
var constrainedSize = x.Constrain(s_screenSize);
@@ -68,7 +69,7 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.Show(true, It.IsAny())).Callback(() =>
{
- windowImpl.Object.Resized?.Invoke(windowImpl.Object.ClientSize, PlatformResizeReason.Unspecified);
+ windowImpl.Object.Resized?.Invoke(windowImpl.Object.ClientSize, WindowResizeReason.Unspecified);
windowImpl.Object.Activated?.Invoke();
});
@@ -87,7 +88,7 @@ namespace Avalonia.UnitTests
{
clientSize = size.Constrain(s_screenSize);
popupImpl.Object.PositionChanged?.Invoke(pos);
- popupImpl.Object.Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified);
+ popupImpl.Object.Resized?.Invoke(clientSize, WindowResizeReason.Unspecified);
});
var positioner = new ManagedPopupPositioner(positionerHelper);