From bc6773f93052300d30cbd1badba09d8b4e056db4 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 14 Apr 2023 20:47:13 +0200 Subject: [PATCH 01/17] Fix InputContext event handling --- native/Avalonia.Native/src/OSX/AvnView.mm | 26 +++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index fdc144e3a5..1c950f01a8 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -521,9 +521,17 @@ - (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) doCommandBySelector:(SEL)selector{ + } - (void)keyUp:(NSEvent *)event @@ -578,6 +586,8 @@ - (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + _lastKeyHandled = true; + if([string isKindOfClass:[NSAttributedString class]]) { _markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; @@ -619,11 +629,15 @@ - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { + _lastKeyHandled = true; + [self unmarkText]; if(_parent != nullptr) { - _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]); + uint32_t timestamp = static_cast([NSDate timeIntervalSinceReferenceDate] * 1000); + + _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [string UTF8String]); } [[self inputContext] invalidateCharacterCoordinates]; @@ -746,9 +760,9 @@ } - (void) setText:(NSString *)text{ - [_text setString:text]; + [self unmarkText]; - [[self inputContext] discardMarkedText]; + [_text setString:text]; } - (void) setSelection:(int)start :(int)end{ From be40f919595cb5071a7da935365e72c8c5dfe14c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 17 Apr 2023 12:28:49 +0200 Subject: [PATCH 02/17] Minor tweaks --- native/Avalonia.Native/src/OSX/AvnView.mm | 34 +++++++++++++---------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index 1c950f01a8..f29508c851 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -22,7 +22,7 @@ AvnPlatformResizeReason _resizeReason; AvnAccessibilityElement* _accessibilityChild; NSRect _cursorRect; - NSMutableString* _text; + NSMutableAttributedString* _text; NSRange _selection; } @@ -59,6 +59,11 @@ [self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]]; _modifierState = AvnInputModifiersNone; + + _text = [[NSMutableAttributedString alloc] initWithString:@""]; + _markedText = [[NSMutableAttributedString alloc] initWithString:@""]; + _selection = NSMakeRange(NSNotFound, 0); + return self; } @@ -530,10 +535,6 @@ } } -- (void) doCommandBySelector:(SEL)selector{ - -} - - (void)keyUp:(NSEvent *)event { [self keyboardEvent:event withType:KeyUp]; @@ -575,7 +576,7 @@ - (NSRange)markedRange { if([_markedText length] > 0) - return NSMakeRange(0, [_markedText length] - 1); + return NSMakeRange(_selection.location, [_markedText length]); return NSMakeRange(NSNotFound, 0); } @@ -608,8 +609,11 @@ { [[_markedText mutableString] setString:@""]; - [[self inputContext] discardMarkedText]; - + if([self inputContext]) { + [[self inputContext] discardMarkedText]; + [[self inputContext] invalidateCharacterCoordinates]; + } + if(!_parent->InputMethod->IsActive()){ return; } @@ -631,8 +635,6 @@ { _lastKeyHandled = true; - [self unmarkText]; - if(_parent != nullptr) { uint32_t timestamp = static_cast([NSDate timeIntervalSinceReferenceDate] * 1000); @@ -640,7 +642,7 @@ _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [string UTF8String]); } - [[self inputContext] invalidateCharacterCoordinates]; + //[self unmarkText]; } - (NSUInteger)characterIndexForPoint:(NSPoint)point @@ -762,13 +764,15 @@ - (void) setText:(NSString *)text{ [self unmarkText]; - [_text setString:text]; + [[_text mutableString] setString:text]; } - (void) setSelection:(int)start :(int)end{ _selection = NSMakeRange(start, end - start); - [[self inputContext] invalidateCharacterCoordinates]; + if([self inputContext]) { + [[self inputContext] invalidateCharacterCoordinates]; + } } - (void) setCursorRect:(AvnRect)rect{ @@ -780,7 +784,9 @@ _cursorRect = windowRectOnScreen; - [[self inputContext] invalidateCharacterCoordinates]; + if([self inputContext]) { + [[self inputContext] invalidateCharacterCoordinates]; + } } @end From 6da9f884de1fc47d4ae638eba438c60aba962cd3 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 19 Apr 2023 06:54:09 +0200 Subject: [PATCH 03/17] More fixes --- native/Avalonia.Native/src/OSX/AvnView.mm | 86 +++++++++++++---------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index f29508c851..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; @@ -23,7 +22,8 @@ AvnAccessibilityElement* _accessibilityChild; NSRect _cursorRect; NSMutableAttributedString* _text; - NSRange _selection; + NSRange _selectedRange; + NSRange _markedRange; } - (void)onClosed @@ -61,8 +61,8 @@ _modifierState = AvnInputModifiersNone; _text = [[NSMutableAttributedString alloc] initWithString:@""]; - _markedText = [[NSMutableAttributedString alloc] initWithString:@""]; - _selection = NSMakeRange(NSNotFound, 0); + _markedRange = NSMakeRange(0, 0); + _selectedRange = NSMakeRange(0, 0); return self; } @@ -541,6 +541,10 @@ [super keyUp:event]; } +- (void) doCommandBySelector:(SEL)selector{ + +} + - (AvnInputModifiers)getModifiers:(NSEventModifierFlags)mod { unsigned int rv = 0; @@ -570,55 +574,52 @@ - (BOOL)hasMarkedText { - return [_markedText length] > 0; + return _markedRange.length > 0; } - (NSRange)markedRange { - if([_markedText length] > 0) - return NSMakeRange(_selection.location, [_markedText length]); - 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); + } + + _markedRange = NSMakeRange(_selectedRange.location, 0); if([self inputContext]) { [[self inputContext] discardMarkedText]; - [[self inputContext] invalidateCharacterCoordinates]; - } - - if(!_parent->InputMethod->IsActive()){ - return; } - - _parent->InputMethod->Client->SetPreeditText(nullptr); } - (NSArray *)validAttributesForMarkedText @@ -628,21 +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 { - _lastKeyHandled = true; + if(_parent == nullptr){ + return; + } - if(_parent != nullptr) - { - uint32_t timestamp = static_cast([NSDate timeIntervalSinceReferenceDate] * 1000); + NSString* text; - _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [string UTF8String]); + if([string isKindOfClass:[NSAttributedString class]]) + { + text = [string string]; + } + else + { + text = (NSString*) string; } - //[self unmarkText]; + [self unmarkText]; + + uint32_t timestamp = static_cast([NSDate timeIntervalSinceReferenceDate] * 1000); + + _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [text UTF8String]); + } - (NSUInteger)characterIndexForPoint:(NSPoint)point @@ -762,17 +780,11 @@ } - (void) setText:(NSString *)text{ - [self unmarkText]; - [[_text mutableString] setString:text]; } - (void) setSelection:(int)start :(int)end{ - _selection = NSMakeRange(start, end - start); - - if([self inputContext]) { - [[self inputContext] invalidateCharacterCoordinates]; - } + _selectedRange = NSMakeRange(start, end - start); } - (void) setCursorRect:(AvnRect)rect{ From a24e0185fc46209dd308fb06d2fe40d3f00f61f2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 18:09:18 +0600 Subject: [PATCH 04/17] Generate fake ref assemblies with patched *Impl and [NotClientImplementable] interfaces --- nukebuild/Build.cs | 2 + nukebuild/Helpers.cs | 24 ++++++++ nukebuild/RefAssemblyGenerator.cs | 99 +++++++++++++++++++++++++++++++ nukebuild/_build.csproj | 14 ++--- 4 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 nukebuild/Helpers.cs create mode 100644 nukebuild/RefAssemblyGenerator.cs diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 40232947d9..630c532686 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -273,6 +273,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..912f74cdf9 --- /dev/null +++ b/nukebuild/RefAssemblyGenerator.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.Writer; + +public class RefAssemblyGenerator +{ + 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 = AssemblyDef.Load(new MemoryStream(File.ReadAllBytes(file))); + + foreach(var t in def.ManifestModule.Types) + ProcessType(t); + def.Write(file, new ModuleWriterOptions(def.ManifestModule) + { + StrongNameKey = new StrongNameKey(snk), + }); + } + + static void ProcessType(TypeDef type) + { + foreach (var nested in type.NestedTypes) + ProcessType(nested); + if (type.IsInterface) + { + var hideMethods = type.Name.EndsWith("Impl"); + var injectMethod = hideMethods + || type.CustomAttributes.Any(a => + a.AttributeType.FullName.EndsWith("NotClientImplementableAttribute")); + + if (hideMethods) + { + foreach (var m in type.Methods) + { + m.Attributes |= MethodAttributes.Public | MethodAttributes.Assembly; + m.Attributes ^= MethodAttributes.Public; + } + } + + if(injectMethod) + { + type.Methods.Add(new MethodDefUser("NotClientImplementable", + new MethodSig(CallingConvention.Default, 0, type.Module.CorLibTypes.Void), + MethodAttributes.Assembly + | MethodAttributes.Abstract + | MethodAttributes.NewSlot + | MethodAttributes.HideBySig)); + } + } + } + + 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/")) + { + if (entry.Name.EndsWith(".dll")) + { + using (Helpers.UseTempDir(out var temp)) + { + var file = Path.Combine(temp, entry.Name); + entry.ExtractToFile(file); + PatchRefAssembly(file); + archive.CreateEntryFromFile(file, "ref/" + entry.FullName.Substring(4)); + + } + } + else if (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); + } + } + } + } + } +} \ No newline at end of file diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 13bac4b7db..cc3ce9f0b0 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -18,6 +18,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -31,18 +32,11 @@ - - - - - - - + + - - - + From f9955f0c79aaed922761646b2c58e9214e9a8b11 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 18:57:45 +0600 Subject: [PATCH 05/17] More patches --- nukebuild/RefAssemblyGenerator.cs | 49 +++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 912f74cdf9..5c5324ac8f 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -18,31 +18,39 @@ public class RefAssemblyGenerator var def = AssemblyDef.Load(new MemoryStream(File.ReadAllBytes(file))); + var obsoleteAttribute = new TypeRefUser(def.ManifestModule, "System", "ObsoleteAttribute", def.ManifestModule.CorLibTypes.AssemblyRef); + var obsoleteCtor = def.ManifestModule.Import(new MemberRefUser(def.ManifestModule, ".ctor", + new MethodSig(CallingConvention.Default, 0, def.ManifestModule.CorLibTypes.Void, new TypeSig[] + { + def.ManifestModule.CorLibTypes.String + }), obsoleteAttribute)); + foreach(var t in def.ManifestModule.Types) - ProcessType(t); + ProcessType(t, obsoleteCtor); def.Write(file, new ModuleWriterOptions(def.ManifestModule) { StrongNameKey = new StrongNameKey(snk), }); } - static void ProcessType(TypeDef type) + static void ProcessType(TypeDef type, MemberRef obsoleteCtor) { foreach (var nested in type.NestedTypes) - ProcessType(nested); + ProcessType(nested, obsoleteCtor); if (type.IsInterface) { var hideMethods = type.Name.EndsWith("Impl"); var injectMethod = hideMethods || type.CustomAttributes.Any(a => a.AttributeType.FullName.EndsWith("NotClientImplementableAttribute")); - + if (hideMethods) { foreach (var m in type.Methods) { - m.Attributes |= MethodAttributes.Public | MethodAttributes.Assembly; - m.Attributes ^= MethodAttributes.Public; + var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem | + MethodAttributes.FamANDAssem | MethodAttributes.Assembly; + m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly; } } @@ -55,8 +63,37 @@ public class RefAssemblyGenerator | MethodAttributes.NewSlot | MethodAttributes.HideBySig)); } + + var forceUnstable = type.CustomAttributes.Any(a => + a.AttributeType.FullName.EndsWith("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(IMemberDef def, MemberRef obsoleteCtor, bool force) + { + if (!force + || def.HasCustomAttributes == false + || !def.CustomAttributes.Any(a => + a.AttributeType.FullName.EndsWith("UnstableAttribute"))) + return; + + if (def.CustomAttributes.Any(a => a.TypeFullName.EndsWith("ObsoleteAttribute"))) + return; + + def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor, new CAArgument[] + { + new(def.Module.CorLibTypes.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) { From 30064443b14175ccbe901c0d331cd42284a9f119 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 20:16:34 +0600 Subject: [PATCH 06/17] Added [PrivateApi] --- nukebuild/RefAssemblyGenerator.cs | 16 +++++++++------- .../Metadata/PrivateApiAttribute.cs | 9 +++++++++ src/Avalonia.Base/Platform/ICursorFactory.cs | 2 ++ .../Platform/IPlatformRenderInterface.cs | 4 ++-- .../Platform/IPlatformIconLoader.cs | 2 +- .../Platform/IWindowingPlatform.cs | 2 +- 6 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 src/Avalonia.Base/Metadata/PrivateApiAttribute.cs diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 5c5324ac8f..61cb04c438 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -39,10 +39,13 @@ public class RefAssemblyGenerator ProcessType(nested, obsoleteCtor); if (type.IsInterface) { - var hideMethods = type.Name.EndsWith("Impl"); + 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.EndsWith("NotClientImplementableAttribute")); + a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute"); if (hideMethods) { @@ -65,7 +68,7 @@ public class RefAssemblyGenerator } var forceUnstable = type.CustomAttributes.Any(a => - a.AttributeType.FullName.EndsWith("UnstableAttribute")); + a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute"); foreach (var m in type.Methods) MarkAsUnstable(m, obsoleteCtor, forceUnstable); @@ -81,11 +84,10 @@ public class RefAssemblyGenerator { if (!force || def.HasCustomAttributes == false - || !def.CustomAttributes.Any(a => - a.AttributeType.FullName.EndsWith("UnstableAttribute"))) + || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")) return; - - if (def.CustomAttributes.Any(a => a.TypeFullName.EndsWith("ObsoleteAttribute"))) + + if (def.CustomAttributes.Any(a => a.TypeFullName == "System.ObsoleteAttribute")) return; def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor, new CAArgument[] diff --git a/src/Avalonia.Base/Metadata/PrivateApiAttribute.cs b/src/Avalonia.Base/Metadata/PrivateApiAttribute.cs new file mode 100644 index 0000000000..3f60940c5e --- /dev/null +++ b/src/Avalonia.Base/Metadata/PrivateApiAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Avalonia.Metadata; + +[AttributeUsage(AttributeTargets.Interface)] +public sealed class PrivateApiAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/ICursorFactory.cs b/src/Avalonia.Base/Platform/ICursorFactory.cs index fff1f92d53..99a9a9d7fa 100644 --- a/src/Avalonia.Base/Platform/ICursorFactory.cs +++ b/src/Avalonia.Base/Platform/ICursorFactory.cs @@ -1,9 +1,11 @@ using Avalonia.Input; +using Avalonia.Metadata; #nullable enable namespace Avalonia.Platform { + [PrivateApi] public interface ICursorFactory { ICursorImpl GetCursor(StandardCursorType cursorType); diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 81fe2c046f..6f62c3be1d 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -11,7 +11,7 @@ namespace Avalonia.Platform /// /// Defines the main platform-specific interface for the rendering subsystem. /// - [Unstable] + [Unstable, PrivateApi] public interface IPlatformRenderInterface { /// @@ -201,7 +201,7 @@ namespace Avalonia.Platform bool IsSupportedBitmapPixelFormat(PixelFormat format); } - [Unstable] + [Unstable, PrivateApi] public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDisposable { /// 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/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(); From 0f7fba7f7f5e5c99708e68c887d9c39e91ada894 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 20:53:24 +0600 Subject: [PATCH 07/17] SourceLink? --- nukebuild/RefAssemblyGenerator.cs | 131 +++++++++++++++++++----------- nukebuild/_build.csproj | 1 - 2 files changed, 82 insertions(+), 50 deletions(-) diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 61cb04c438..2c5724e3ab 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -1,39 +1,69 @@ -using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using dnlib.DotNet.Writer; +using ILRepacking; +using Mono.Cecil; +using Mono.Cecil.Cil; public class RefAssemblyGenerator { - static void PatchRefAssembly(string file) + 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 = AssemblyDef.Load(new MemoryStream(File.ReadAllBytes(file))); - - var obsoleteAttribute = new TypeRefUser(def.ManifestModule, "System", "ObsoleteAttribute", def.ManifestModule.CorLibTypes.AssemblyRef); - var obsoleteCtor = def.ManifestModule.Import(new MemberRefUser(def.ManifestModule, ".ctor", - new MethodSig(CallingConvention.Default, 0, def.ManifestModule.CorLibTypes.Void, new TypeSig[] - { - def.ManifestModule.CorLibTypes.String - }), obsoleteAttribute)); - - foreach(var t in def.ManifestModule.Types) + + 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 ModuleWriterOptions(def.ManifestModule) + def.Write(file, new WriterParameters() { - StrongNameKey = new StrongNameKey(snk), + StrongNameKeyBlob = snk, + WriteSymbols = def.MainModule.HasSymbols, + SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(), + DeterministicMvid = def.MainModule.HasSymbols }); } - static void ProcessType(TypeDef type, MemberRef obsoleteCtor) + static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor) { foreach (var nested in type.NestedTypes) ProcessType(nested, obsoleteCtor); @@ -59,12 +89,11 @@ public class RefAssemblyGenerator if(injectMethod) { - type.Methods.Add(new MethodDefUser("NotClientImplementable", - new MethodSig(CallingConvention.Default, 0, type.Module.CorLibTypes.Void), + type.Methods.Add(new MethodDefinition("NotClientImplementable", MethodAttributes.Assembly | MethodAttributes.Abstract | MethodAttributes.NewSlot - | MethodAttributes.HideBySig)); + | MethodAttributes.HideBySig, type.Module.TypeSystem.Void)); } var forceUnstable = type.CustomAttributes.Any(a => @@ -80,21 +109,24 @@ public class RefAssemblyGenerator } } - static void MarkAsUnstable(IMemberDef def, MemberRef obsoleteCtor, bool force) + 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.TypeFullName == "System.ObsoleteAttribute")) + if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) return; - def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor, new CAArgument[] + def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor) { - new(def.Module.CorLibTypes.String, - "This is a part of unstable API and can be changed in minor releases. You have been warned") - })); + 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) @@ -110,29 +142,30 @@ public class RefAssemblyGenerator foreach (var entry in archive.Entries.ToList()) { - if (entry.FullName.StartsWith("lib/")) + if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml")) { - if (entry.Name.EndsWith(".dll")) - { - using (Helpers.UseTempDir(out var temp)) - { - var file = Path.Combine(temp, entry.Name); - entry.ExtractToFile(file); - PatchRefAssembly(file); - archive.CreateEntryFromFile(file, "ref/" + entry.FullName.Substring(4)); - - } - } - else if (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 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 cc3ce9f0b0..d03746766e 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -18,7 +18,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive From c5ae8bb762589850c1620ca2b07b7169a72515f9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 21:40:26 +0600 Subject: [PATCH 08/17] Fixes --- nukebuild/RefAssemblyGenerator.cs | 6 +++--- src/Avalonia.Base/Threading/IDispatcherImpl.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 2c5724e3ab..cbe5236bca 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -111,9 +111,9 @@ public class RefAssemblyGenerator static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force) { - if (!force - || def.HasCustomAttributes == false - || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")) + 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")) diff --git a/src/Avalonia.Base/Threading/IDispatcherImpl.cs b/src/Avalonia.Base/Threading/IDispatcherImpl.cs index 4c30e2eb2c..ccbe3baf9a 100644 --- a/src/Avalonia.Base/Threading/IDispatcherImpl.cs +++ b/src/Avalonia.Base/Threading/IDispatcherImpl.cs @@ -6,7 +6,7 @@ using Avalonia.Platform; namespace Avalonia.Threading; -[Unstable] +[PrivateApi] public interface IDispatcherImpl { bool CurrentThreadIsLoopThread { get; } @@ -19,7 +19,7 @@ public interface IDispatcherImpl void UpdateTimer(long? dueTimeInMs); } -[Unstable] +[PrivateApi] public interface IDispatcherImplWithPendingInput : IDispatcherImpl { // Checks if dispatcher implementation can @@ -28,14 +28,14 @@ public interface IDispatcherImplWithPendingInput : IDispatcherImpl bool HasPendingInput { get; } } -[Unstable] +[PrivateApi] public interface IDispatcherImplWithExplicitBackgroundProcessing : IDispatcherImpl { event Action ReadyForBackgroundProcessing; void RequestBackgroundProcessing(); } -[Unstable] +[PrivateApi] public interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput { // Runs the event loop From e9519e27192f87ac57e5df882870d1b24a10a312 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 19 Apr 2023 19:58:34 -0700 Subject: [PATCH 09/17] Fix NET8 build --- Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 38c1cc95c6596d3638bac4244d0bf87e98733eba Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 8 Mar 2023 22:41:16 +0900 Subject: [PATCH 10/17] Init AvaloniaListAttribute --- .../Metadata/AvaloniaListAttribute.cs | 12 ++ src/Avalonia.Controls/Shapes/Polygon.cs | 7 +- src/Avalonia.Controls/Shapes/Polyline.cs | 6 +- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 118 ++++++++++++------ .../AvaloniaXamlIlWellKnownTypes.cs | 4 + 5 files changed, 99 insertions(+), 48 deletions(-) create mode 100644 src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs diff --git a/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs new file mode 100644 index 0000000000..f06e6f1ca9 --- /dev/null +++ b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.Metadata; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public sealed class AvaloniaListAttribute : Attribute +{ + public string[]? Separators { get; init; } + + // StringSplitOptions.TrimEntries = 2, but only on net6 target. + public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2; +} diff --git a/src/Avalonia.Controls/Shapes/Polygon.cs b/src/Avalonia.Controls/Shapes/Polygon.cs index 70a45f3516..3ac5af2d33 100644 --- a/src/Avalonia.Controls/Shapes/Polygon.cs +++ b/src/Avalonia.Controls/Shapes/Polygon.cs @@ -1,19 +1,18 @@ -using System.Collections.Generic; using Avalonia.Media; namespace Avalonia.Controls.Shapes { public class Polygon : Shape { - public static readonly StyledProperty> PointsProperty = - AvaloniaProperty.Register>("Points"); + public static readonly StyledProperty PointsProperty = + AvaloniaProperty.Register("Points"); static Polygon() { AffectsGeometry(PointsProperty); } - public IList Points + public Points Points { get { return GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } diff --git a/src/Avalonia.Controls/Shapes/Polyline.cs b/src/Avalonia.Controls/Shapes/Polyline.cs index 4b4bb3ffd0..e6edd7a599 100644 --- a/src/Avalonia.Controls/Shapes/Polyline.cs +++ b/src/Avalonia.Controls/Shapes/Polyline.cs @@ -5,8 +5,8 @@ namespace Avalonia.Controls.Shapes { public class Polyline: Shape { - public static readonly StyledProperty> PointsProperty = - AvaloniaProperty.Register>("Points"); + public static readonly StyledProperty PointsProperty = + AvaloniaProperty.Register("Points"); static Polyline() { @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes AffectsGeometry(PointsProperty); } - public IList Points + public Points Points { get { return GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index d8524cfd88..65fa6f3e8b 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,69 @@ 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 itemType = GetElementType(type, context.Configuration.WellKnownTypes); + if (itemType is not null + && types.AvaloniaList.MakeGenericType(itemType).IsAssignableFrom(type)) { - var lengths = GridLength.ParseLengths(text); - - var definitionTypeRef = new XamlAstClrTypeReference(node, elementType, false); - - var definitionConstructorGridLength = elementType.GetConstructor(new List {types.GridLength}); + const StringSplitOptions trimOption = (StringSplitOptions)2; // StringSplitOptions.TrimEntries + var separators = new[] { "," }; + var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption; - IXamlAstValueNode CreateDefinitionNode(GridLength length) + var attribute = type.CustomAttributes.FirstOrDefault(a => a.Type == types.AvaloniaListAttribute); + if (attribute is not null) { - var lengthNode = new AvaloniaXamlIlGridLengthAstNode(node, types, length); - - return new XamlAstNewClrObjectNode(node, definitionTypeRef, - definitionConstructorGridLength, new List {lengthNode}); + if (attribute.Properties.TryGetValue("Separators", out var separatorsArray)) + { + separators = ((Array)separatorsArray)?.OfType().ToArray(); + } + if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj)) + { + splitOptions = (StringSplitOptions)splitOptionsObj; + } + } + + var 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(); } - var definitionNodes = - new List(lengths.Select(CreateDefinitionNode)); + if (itemType is null) + { + throw new XamlX.XamlLoadException($"Type '{type.Name}' is not a collection type.", node); + } - result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, listType, elementType, definitionNodes); + var nodes = new IXamlAstValueNode[items.Length]; + for (var index = 0; index < items.Length; index++) + { + var success = XamlTransformHelpers.TryGetCorrectlyTypedValue( + context, + new XamlAstTextNode(node, items[index], true, context.Configuration.WellKnownTypes.String), + itemType, out var itemNode); + if (!success) + { + result = null; + return false; + } + nodes[index] = itemNode; + } + + result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, itemType, nodes); return true; } - catch - { - throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a {errorDisplayName}", node); - } + + 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/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 60a7d953ab..62ba2eb5a2 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,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType InheritDataTypeFromItemsAttribute { get; } public IXamlType MarkupExtensionOptionAttribute { get; } public IXamlType MarkupExtensionDefaultOptionAttribute { get; } + public IXamlType AvaloniaListAttribute { get; } + public IXamlType AvaloniaList { get; } public IXamlType OnExtensionType { get; } public IXamlType UnsetValueType { get; } public IXamlType StyledElement { get; } @@ -141,6 +143,8 @@ 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"); + AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute"); + 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, From 862d07fcaf8a50342f8ee96174482b6c7984b8f3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 20 Apr 2023 01:34:25 -0400 Subject: [PATCH 11/17] Support special case for points collection --- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 56 ++++++++++++------- .../Shapes/PolygonTests.cs | 4 +- .../Shapes/PolylineTests.cs | 4 +- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 65fa6f3e8b..e88199cdad 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -313,33 +313,51 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions if (itemType is not null && types.AvaloniaList.MakeGenericType(itemType).IsAssignableFrom(type)) { - const StringSplitOptions trimOption = (StringSplitOptions)2; // StringSplitOptions.TrimEntries - var separators = new[] { "," }; - var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption; - - var attribute = type.CustomAttributes.FirstOrDefault(a => a.Type == types.AvaloniaListAttribute); - if (attribute is not null) + string[] items; + // Normalize special case of Points collection. + if (itemType == types.Point) { - if (attribute.Properties.TryGetValue("Separators", out var separatorsArray)) + var pointParts = text.Split(new[] { ",", " " }, StringSplitOptions.RemoveEmptyEntries); + if (pointParts.Length % 2 == 0) { - separators = ((Array)separatorsArray)?.OfType().ToArray(); + 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]); + } } - if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj)) + else { - splitOptions = (StringSplitOptions)splitOptionsObj; + throw new XamlX.XamlLoadException($"Invalid PointsList.", node); } } - - var items = text.Split(separators, splitOptions ^ trimOption); - // Compiler targets netstandard, so we need to emulate StringSplitOptions.TrimEntries, if it was requested. - if (splitOptions.HasFlag(trimOption)) + else { - items = items.Select(i => i.Trim()).ToArray(); - } + const StringSplitOptions trimOption = (StringSplitOptions)2; // StringSplitOptions.TrimEntries + var separators = new[] { "," }; + var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption; - if (itemType is null) - { - throw new XamlX.XamlLoadException($"Type '{type.Name}' is not a collection type.", node); + var attribute = type.CustomAttributes.FirstOrDefault(a => a.Type == types.AvaloniaListAttribute); + if (attribute is not null) + { + if (attribute.Properties.TryGetValue("Separators", out var separatorsArray)) + { + separators = ((Array)separatorsArray)?.OfType().ToArray(); + } + + if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj)) + { + splitOptions = (StringSplitOptions)splitOptionsObj; + } + } + + 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(); + } } var nodes = new IXamlAstValueNode[items.Length]; 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 From 477abdd2f02d21025af9afe27881083c92be1c33 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 20 Apr 2023 02:19:00 -0400 Subject: [PATCH 12/17] Remove AvaloniaListAttribute --- .../Metadata/AvaloniaListAttribute.cs | 12 ------------ .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 14 -------------- .../Transformers/AvaloniaXamlIlWellKnownTypes.cs | 2 -- 3 files changed, 28 deletions(-) delete mode 100644 src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs diff --git a/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs deleted file mode 100644 index f06e6f1ca9..0000000000 --- a/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Avalonia.Metadata; - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] -public sealed class AvaloniaListAttribute : Attribute -{ - public string[]? Separators { get; init; } - - // StringSplitOptions.TrimEntries = 2, but only on net6 target. - public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2; -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index e88199cdad..ded1953dff 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -338,20 +338,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var separators = new[] { "," }; var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption; - var attribute = type.CustomAttributes.FirstOrDefault(a => a.Type == types.AvaloniaListAttribute); - if (attribute is not null) - { - if (attribute.Properties.TryGetValue("Separators", out var separatorsArray)) - { - separators = ((Array)separatorsArray)?.OfType().ToArray(); - } - - if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj)) - { - splitOptions = (StringSplitOptions)splitOptionsObj; - } - } - items = text.Split(separators, splitOptions ^ trimOption); // Compiler targets netstandard, so we need to emulate StringSplitOptions.TrimEntries, if it was requested. if (splitOptions.HasFlag(trimOption)) 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 62ba2eb5a2..63683da0db 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -33,7 +33,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType InheritDataTypeFromItemsAttribute { get; } public IXamlType MarkupExtensionOptionAttribute { get; } public IXamlType MarkupExtensionDefaultOptionAttribute { get; } - public IXamlType AvaloniaListAttribute { get; } public IXamlType AvaloniaList { get; } public IXamlType OnExtensionType { get; } public IXamlType UnsetValueType { get; } @@ -143,7 +142,6 @@ 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"); - AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute"); AvaloniaList = cfg.TypeSystem.GetType("Avalonia.Collections.AvaloniaList`1"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, AvaloniaObject, From c2b280348341d22ae63bda55891b7822eac7f32c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 20 Apr 2023 02:26:05 -0400 Subject: [PATCH 13/17] Nullable attributes --- src/Avalonia.Controls/Shapes/Polygon.cs | 6 +++--- src/Avalonia.Controls/Shapes/Polyline.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Polygon.cs b/src/Avalonia.Controls/Shapes/Polygon.cs index 3ac5af2d33..1a9dc42490 100644 --- a/src/Avalonia.Controls/Shapes/Polygon.cs +++ b/src/Avalonia.Controls/Shapes/Polygon.cs @@ -4,15 +4,15 @@ namespace Avalonia.Controls.Shapes { public class Polygon : Shape { - public static readonly StyledProperty PointsProperty = - AvaloniaProperty.Register("Points"); + public static readonly StyledProperty PointsProperty = + AvaloniaProperty.Register("Points"); static Polygon() { AffectsGeometry(PointsProperty); } - public Points Points + public Points? Points { get { return GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } diff --git a/src/Avalonia.Controls/Shapes/Polyline.cs b/src/Avalonia.Controls/Shapes/Polyline.cs index e6edd7a599..95ab880142 100644 --- a/src/Avalonia.Controls/Shapes/Polyline.cs +++ b/src/Avalonia.Controls/Shapes/Polyline.cs @@ -5,8 +5,8 @@ namespace Avalonia.Controls.Shapes { public class Polyline: Shape { - public static readonly StyledProperty PointsProperty = - AvaloniaProperty.Register("Points"); + public static readonly StyledProperty PointsProperty = + AvaloniaProperty.Register("Points"); static Polyline() { @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes AffectsGeometry(PointsProperty); } - public Points Points + public Points? Points { get { return GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } From e5acebabcbf8163642a633ee0d9fc696eec17fc1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 20 Apr 2023 18:20:49 +0200 Subject: [PATCH 14/17] Make thumb drag delta relative to root. #10892 changed the thumb drag delta to be relative to the parent, but the problem was that is that if the thumb controls the position of the parent then the delta will be incorrect. This was causing a bug in `TreeDataGrid` headers which were jumping around: the `Thumb` is a child of the header and causes the header to move. --- src/Avalonia.Controls/Primitives/Thumb.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index 993d054f87..5dd2bd067b 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -85,7 +85,7 @@ namespace Avalonia.Controls.Primitives { if (_lastPoint.HasValue) { - var point = e.GetPosition(this.GetVisualParent()); + var point = e.GetPosition((Visual?)this.GetVisualRoot()); var ev = new VectorEventArgs { RoutedEvent = DragDeltaEvent, @@ -100,7 +100,7 @@ namespace Avalonia.Controls.Primitives protected override void OnPointerPressed(PointerPressedEventArgs e) { e.Handled = true; - _lastPoint = e.GetPosition(this.GetVisualParent()); + _lastPoint = e.GetPosition((Visual?)this.GetVisualRoot()); var ev = new VectorEventArgs { @@ -123,7 +123,7 @@ namespace Avalonia.Controls.Primitives var ev = new VectorEventArgs { RoutedEvent = DragCompletedEvent, - Vector = (Vector)e.GetPosition(this.GetVisualParent()), + Vector = (Vector)e.GetPosition((Visual?)this.GetVisualRoot()), }; RaiseEvent(ev); From ec19a0876e925a98083a15e8ae5009e22875d789 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 20 Apr 2023 21:22:36 -0400 Subject: [PATCH 15/17] Cleanup Points collection usage, make it use IList --- src/Avalonia.Base/Media/PolyLineSegment.cs | 10 +++++----- src/Avalonia.Base/Media/PolylineGeometry.cs | 18 +++++++++--------- src/Avalonia.Base/Points.cs | 14 +++++++++++++- src/Avalonia.Controls/Shapes/Polygon.cs | 16 +++++++++++----- src/Avalonia.Controls/Shapes/Polyline.cs | 15 ++++++++++----- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Base/Media/PolyLineSegment.cs b/src/Avalonia.Base/Media/PolyLineSegment.cs index 5c48c11e19..d17a621348 100644 --- a/src/Avalonia.Base/Media/PolyLineSegment.cs +++ b/src/Avalonia.Base/Media/PolyLineSegment.cs @@ -10,8 +10,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty PointsProperty - = AvaloniaProperty.Register(nameof(Points)); + public static readonly StyledProperty> PointsProperty + = AvaloniaProperty.Register>(nameof(Points)); /// /// Gets or sets the points. @@ -19,7 +19,7 @@ namespace Avalonia.Media /// /// The points. /// - public Points Points + public IList Points { get => GetValue(PointsProperty); set => SetValue(PointsProperty, value); @@ -37,9 +37,9 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// /// The points. - public PolyLineSegment(IEnumerable points) : this() + public PolyLineSegment(IEnumerable points) { - Points.AddRange(points); + Points = new Points(points); } protected internal override void ApplyTo(StreamGeometryContext ctx) diff --git a/src/Avalonia.Base/Media/PolylineGeometry.cs b/src/Avalonia.Base/Media/PolylineGeometry.cs index dd3c298b5b..b0229b6455 100644 --- a/src/Avalonia.Base/Media/PolylineGeometry.cs +++ b/src/Avalonia.Base/Media/PolylineGeometry.cs @@ -14,8 +14,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly DirectProperty PointsProperty = - AvaloniaProperty.RegisterDirect(nameof(Points), g => g.Points, (g, f) => g.Points = f); + public static readonly DirectProperty> PointsProperty = + AvaloniaProperty.RegisterDirect>(nameof(Points), g => g.Points, (g, f) => g.Points = f); /// /// Defines the property. @@ -23,13 +23,13 @@ namespace Avalonia.Media public static readonly StyledProperty IsFilledProperty = AvaloniaProperty.Register(nameof(IsFilled)); - private Points _points; + private IList _points; private IDisposable? _pointsObserver; static PolylineGeometry() { AffectsGeometry(IsFilledProperty); - PointsProperty.Changed.AddClassHandler((s, e) => s.OnPointsChanged(e.NewValue as Points)); + PointsProperty.Changed.AddClassHandler((s, e) => s.OnPointsChanged(e.NewValue as IList)); } /// @@ -43,9 +43,9 @@ namespace Avalonia.Media /// /// Initializes a new instance of the class. /// - public PolylineGeometry(IEnumerable points, bool isFilled) : this() + public PolylineGeometry(IEnumerable points, bool isFilled) { - Points.AddRange(points); + _points = new Points(points); IsFilled = isFilled; } @@ -56,7 +56,7 @@ namespace Avalonia.Media /// The points. /// [Content] - public Points Points + public IList Points { get => _points; set => SetAndRaise(PointsProperty, ref _points, value); @@ -97,10 +97,10 @@ namespace Avalonia.Media return geometry; } - private void OnPointsChanged(Points? newValue) + private void OnPointsChanged(IList? newValue) { _pointsObserver?.Dispose(); - _pointsObserver = newValue?.ForEachItem( + _pointsObserver = (newValue as IAvaloniaList)?.ForEachItem( _ => InvalidateGeometry(), _ => InvalidateGeometry(), InvalidateGeometry); diff --git a/src/Avalonia.Base/Points.cs b/src/Avalonia.Base/Points.cs index b655dbcb38..2f88ecd80f 100644 --- a/src/Avalonia.Base/Points.cs +++ b/src/Avalonia.Base/Points.cs @@ -1,6 +1,18 @@ +using System.Collections.Generic; using Avalonia.Collections; namespace Avalonia { - public sealed class Points : AvaloniaList { } + public sealed class Points : AvaloniaList + { + public Points() + { + + } + + public Points(IEnumerable points) : base(points) + { + + } + } } diff --git a/src/Avalonia.Controls/Shapes/Polygon.cs b/src/Avalonia.Controls/Shapes/Polygon.cs index 1a9dc42490..78def84448 100644 --- a/src/Avalonia.Controls/Shapes/Polygon.cs +++ b/src/Avalonia.Controls/Shapes/Polygon.cs @@ -1,21 +1,27 @@ +using System.Collections.Generic; using Avalonia.Media; namespace Avalonia.Controls.Shapes { public class Polygon : Shape { - public static readonly StyledProperty PointsProperty = - AvaloniaProperty.Register("Points"); + public static readonly StyledProperty> PointsProperty = + AvaloniaProperty.Register>("Points"); static Polygon() { AffectsGeometry(PointsProperty); } - public Points? Points + public Polygon() { - get { return GetValue(PointsProperty); } - set { SetValue(PointsProperty, value); } + Points = new Points(); + } + + public IList Points + { + 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 95ab880142..2533794f89 100644 --- a/src/Avalonia.Controls/Shapes/Polyline.cs +++ b/src/Avalonia.Controls/Shapes/Polyline.cs @@ -5,8 +5,8 @@ namespace Avalonia.Controls.Shapes { public class Polyline: Shape { - public static readonly StyledProperty PointsProperty = - AvaloniaProperty.Register("Points"); + public static readonly StyledProperty> PointsProperty = + AvaloniaProperty.Register>("Points"); static Polyline() { @@ -14,10 +14,15 @@ namespace Avalonia.Controls.Shapes AffectsGeometry(PointsProperty); } - public Points? Points + public Polyline() { - get { return GetValue(PointsProperty); } - set { SetValue(PointsProperty, value); } + Points = new Points(); + } + + public IList Points + { + get => GetValue(PointsProperty); + set => SetValue(PointsProperty, value); } protected override Geometry CreateDefiningGeometry() From 56d87931db92f980080302d0966e622e0df5a225 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 20 Apr 2023 21:23:48 -0400 Subject: [PATCH 16/17] Inject array for IList --- .../AvaloniaXamlIlArrayConstantAstNode.cs | 71 +++++++++++++++++++ .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 32 ++++++--- .../Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs 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/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index ded1953dff..cd005ce24d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -309,13 +309,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } // Keep it in the end, so more specific parsers can be applied. - var itemType = GetElementType(type, context.Configuration.WellKnownTypes); - if (itemType is not null - && types.AvaloniaList.MakeGenericType(itemType).IsAssignableFrom(type)) + var elementType = GetElementType(type, context.Configuration.WellKnownTypes); + if (elementType is not null) { string[] items; // Normalize special case of Points collection. - if (itemType == types.Point) + if (elementType == types.Point) { var pointParts = text.Split(new[] { ",", " " }, StringSplitOptions.RemoveEmptyEntries); if (pointParts.Length % 2 == 0) @@ -352,7 +351,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var success = XamlTransformHelpers.TryGetCorrectlyTypedValue( context, new XamlAstTextNode(node, items[index], true, context.Configuration.WellKnownTypes.String), - itemType, out var itemNode); + elementType, out var itemNode); if (!success) { result = null; @@ -361,9 +360,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions nodes[index] = itemNode; } - - result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, itemType, nodes); - return true; + + 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; + } + + result = null; + return false; } result = null; 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 From 5262eec4cf1f5134ac873a67a386bde9678913ab Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 21 Apr 2023 10:38:00 +0200 Subject: [PATCH 17/17] Passing null gives us the point relative to the root. And update the documentation for `GetPosition` to explain what `null` does (as in `GetCurrentPoint`). --- src/Avalonia.Base/Input/PointerEventArgs.cs | 4 ++-- src/Avalonia.Controls/Primitives/Thumb.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 28a3c3aefb..beb953ce8f 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -77,14 +77,14 @@ namespace Avalonia.Input /// /// Gets the pointer position relative to a control. /// - /// The control. + /// The visual whose coordinate system to use. Pass null for toplevel coordinate system /// The pointer position in the control's coordinates. public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); /// /// Returns the PointerPoint associated with the current event /// - /// The visual which coordinate system to use. Pass null for toplevel coordinate system + /// The visual whose coordinate system to use. Pass null for toplevel coordinate system /// public PointerPoint GetCurrentPoint(Visual? relativeTo) => new PointerPoint(Pointer, GetPosition(relativeTo), _properties); diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index 5dd2bd067b..9854bdbea6 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -85,7 +85,7 @@ namespace Avalonia.Controls.Primitives { if (_lastPoint.HasValue) { - var point = e.GetPosition((Visual?)this.GetVisualRoot()); + var point = e.GetPosition(null); var ev = new VectorEventArgs { RoutedEvent = DragDeltaEvent, @@ -100,7 +100,7 @@ namespace Avalonia.Controls.Primitives protected override void OnPointerPressed(PointerPressedEventArgs e) { e.Handled = true; - _lastPoint = e.GetPosition((Visual?)this.GetVisualRoot()); + _lastPoint = e.GetPosition(null); var ev = new VectorEventArgs { @@ -123,7 +123,7 @@ namespace Avalonia.Controls.Primitives var ev = new VectorEventArgs { RoutedEvent = DragCompletedEvent, - Vector = (Vector)e.GetPosition((Visual?)this.GetVisualRoot()), + Vector = (Vector)e.GetPosition(null), }; RaiseEvent(ev);