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..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..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/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.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/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.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.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
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();
diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs
index 993d054f87..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(this.GetVisualParent());
+ 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(this.GetVisualParent());
+ _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(this.GetVisualParent()),
+ Vector = (Vector)e.GetPosition(null),
};
RaiseEvent(ev);
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/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 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/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 60a7d953ab..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,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; }
@@ -141,6 +142,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,
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/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