From 3b18572dd60cc0222ef7717d9455557ce01b1676 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Nov 2020 10:49:34 +0800 Subject: [PATCH 01/33] add property listening for path segments --- src/Avalonia.Visuals/Media/PathFigure.cs | 32 +++++++++++++++++++++- src/Avalonia.Visuals/Media/PathGeometry.cs | 20 ++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index d0eb67ba39..ff63a6286c 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,3 +1,6 @@ +using System; +using System.ComponentModel; +using Avalonia.Collections; using Avalonia.Metadata; namespace Avalonia.Media @@ -25,14 +28,41 @@ namespace Avalonia.Media public static readonly StyledProperty StartPointProperty = AvaloniaProperty.Register(nameof(StartPoint)); + internal event EventHandler SegmentsInvalidated; + + private IDisposable? _segmentsObserver; + + private IDisposable? _segmentsPropertiesObserver; + /// /// Initializes a new instance of the class. /// public PathFigure() { + SegmentsProperty.Changed.AddClassHandler((s, e) => + s.OnSegmentsChanged(e.NewValue as PathSegments)); + Segments = new PathSegments(); } + private void OnSegmentsChanged(PathSegments? arg2NewValue) + { + _segmentsObserver?.Dispose(); + _segmentsPropertiesObserver?.Dispose(); + + _segmentsObserver = _segments?.ForEachItem( + _ => InvalidateSegments(), + _ => InvalidateSegments(), + InvalidateSegments); + + _segmentsPropertiesObserver = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); + } + + private void InvalidateSegments() + { + SegmentsInvalidated?.Invoke(this, EventArgs.Empty); + } + /// /// Gets or sets a value indicating whether this instance is closed. /// @@ -99,4 +129,4 @@ namespace Avalonia.Media public override string ToString() => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index fbc29aedc8..819669d86e 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -104,12 +104,26 @@ namespace Avalonia.Media _figuresPropertiesObserver?.Dispose(); _figuresObserver = figures?.ForEachItem( - _ => InvalidateGeometry(), - _ => InvalidateGeometry(), - () => InvalidateGeometry()); + s => + { + s.SegmentsInvalidated += InvalidateGeometryFromSegments; + InvalidateGeometry(); + }, + s => + { + s.SegmentsInvalidated -= InvalidateGeometryFromSegments; + InvalidateGeometry(); + }, + InvalidateGeometry); + _figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry()); + } + public void InvalidateGeometryFromSegments(object _, EventArgs __) + { + InvalidateGeometry(); + } public override string ToString() => $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}"; From d2339a041df94ca65878140435a351a3932f7ee4 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Nov 2020 11:02:30 +0800 Subject: [PATCH 02/33] try making a unit test --- .../Avalonia.RenderTests/Shapes/PathTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index bac16cca88..24e420d972 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -353,7 +353,47 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task PathSegment_Triggers_Invalidation_On_Property_Change() + { + var targetSegment = new ArcSegment() + { + Size = new Size(10,10), + Point = new Point(5,5) + }; + + var targetPath = new Path + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + Fill = Brushes.Red, + Data = new PathGeometry + { + Figures = new PathFigures + { + new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } + } + } + }; + + var root = new Border + { + Width = 100, + Height = 100, + Background = Brushes.White, + Child =targetPath + }; + + Assert.Equal(10, targetPath.Bounds.Height); + Assert.Equal(10, targetPath.Bounds.Width); + + targetSegment.Size = new Size(20, 20); + Assert.Equal(20, targetPath.Bounds.Height); + Assert.Equal(20, targetPath.Bounds.Width); + } + [Fact] public async Task Path_With_Rotated_Geometry() { From 91791c7d15db039be527936375cef79278d59732 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 07:54:16 +0800 Subject: [PATCH 03/33] Implement RadiusX/Y and center, just like in WPF. --- src/Avalonia.Visuals/Media/EllipseGeometry.cs | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs index fd73eee69a..bae6dc3d8c 100644 --- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs +++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs @@ -12,10 +12,28 @@ namespace Avalonia.Media /// public static readonly StyledProperty RectProperty = AvaloniaProperty.Register(nameof(Rect)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RadiusXProperty = + AvaloniaProperty.Register(nameof(RadiusX)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RadiusYProperty = + AvaloniaProperty.Register(nameof(RadiusY)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterProperty = + AvaloniaProperty.Register(nameof(Center)); static EllipseGeometry() { - AffectsGeometry(RectProperty); + AffectsGeometry(RectProperty, RadiusXProperty, RadiusYProperty, CenterProperty); } /// @@ -43,6 +61,33 @@ namespace Avalonia.Media set => SetValue(RectProperty, value); } + /// + /// Gets or sets a double that defines the radius in the X-axis of the ellipse. + /// + public double RadiusX + { + get => GetValue(RadiusXProperty); + set => SetValue(RadiusXProperty, value); + } + + /// + /// Gets or sets a double that defines the radius in the Y-axis of the ellipse. + /// + public double RadiusY + { + get => GetValue(RadiusYProperty); + set => SetValue(RadiusYProperty, value); + } + + /// + /// Gets or sets a point that defines the center of the ellipse. + /// + public Point Center + { + get => GetValue(CenterProperty); + set => SetValue(CenterProperty, value); + } + /// public override Geometry Clone() { @@ -54,7 +99,14 @@ namespace Avalonia.Media { var factory = AvaloniaLocator.Current.GetService(); - return factory.CreateEllipseGeometry(Rect); + if (Rect != default) return factory.CreateEllipseGeometry(Rect); + + var originX = Center.X - RadiusX; + var originY = Center.Y - RadiusY; + var width = RadiusX * 2; + var height = RadiusY * 2; + + return factory.CreateEllipseGeometry(new Rect(originX, originY, width, height)); } } } From e90a5d285c7bebf478a97b4c357681a049702250 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 16:54:08 +0800 Subject: [PATCH 04/33] add change listener on static ctor --- src/Avalonia.Visuals/Media/PathFigure.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index ff63a6286c..a9e3c6d628 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -38,11 +38,14 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// public PathFigure() + { + Segments = new PathSegments(); + } + + static PathFigure() { SegmentsProperty.Changed.AddClassHandler((s, e) => s.OnSegmentsChanged(e.NewValue as PathSegments)); - - Segments = new PathSegments(); } private void OnSegmentsChanged(PathSegments? arg2NewValue) From 7df1d2ad4d07f30fe41aaf8e477c1be8832f7de4 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 16:58:53 +0800 Subject: [PATCH 05/33] add test, thanks @donandren ! :D --- .../Media/PathSegmentTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs new file mode 100644 index 0000000000..0737b4dc88 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs @@ -0,0 +1,34 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class PathSegmentTests + { + [Fact] + public void PathSegment_Triggers_Invalidation_On_Property_Change() + { + var targetSegment = new ArcSegment() + { + Size = new Size(10, 10), + Point = new Point(5, 5) + }; + + var target = new PathGeometry + { + Figures = new PathFigures + { + new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } + } + }; + + var changed = false; + + target.Changed += (s, e) => changed = true; + + targetSegment.Size = new Size(20, 20); + + Assert.True(changed); + } + } +} From 4e180c88cdb5f4e22e177f41a83010165e7836fb Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 16:59:43 +0800 Subject: [PATCH 06/33] remove old test --- .../Avalonia.RenderTests/Shapes/PathTests.cs | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index 24e420d972..bac16cca88 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -353,47 +353,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes await RenderToFile(target); CompareImages(); } - - [Fact] - public async Task PathSegment_Triggers_Invalidation_On_Property_Change() - { - var targetSegment = new ArcSegment() - { - Size = new Size(10,10), - Point = new Point(5,5) - }; - - var targetPath = new Path - { - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - Fill = Brushes.Red, - Data = new PathGeometry - { - Figures = new PathFigures - { - new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } - } - } - }; - - var root = new Border - { - Width = 100, - Height = 100, - Background = Brushes.White, - Child =targetPath - }; - - Assert.Equal(10, targetPath.Bounds.Height); - Assert.Equal(10, targetPath.Bounds.Width); - - targetSegment.Size = new Size(20, 20); - Assert.Equal(20, targetPath.Bounds.Height); - Assert.Equal(20, targetPath.Bounds.Width); - } - [Fact] public async Task Path_With_Rotated_Geometry() { From 2b9e092b32757f32b028288aac2934027b5ce924 Mon Sep 17 00:00:00 2001 From: capdj <42602325+capdj@users.noreply.github.com> Date: Tue, 17 Nov 2020 15:33:45 +0000 Subject: [PATCH 07/33] Fix X11 dropping modifier keys Fixes #4988 --- src/Avalonia.X11/XI2Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index b3a24e6c37..8cdf24cc7b 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -351,7 +351,7 @@ namespace Avalonia.X11 if (state.HasFlag(XModifierMask.Mod4Mask)) Modifiers |= RawInputModifiers.Meta; - Modifiers = ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask); + Modifiers |= ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask); Valuators = new Dictionary(); Position = new Point(ev->event_x, ev->event_y); From 053537721a65632346ccd55441fdabdcb2e30ef1 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 17 Nov 2020 16:49:55 +0100 Subject: [PATCH 08/33] Parse Color and GridLength during compilation. Improve error handling for failed parsing. --- .../Avalonia.Build.Tasks.csproj | 11 +- src/Avalonia.Build.Tasks/SpanCompat.cs | 4 + src/Avalonia.Controls/GridLength.cs | 10 +- src/Avalonia.Visuals/Media/Color.cs | 14 +- src/Avalonia.Visuals/Media/KnownColors.cs | 9 ++ .../AvaloniaXamlIlGridLengthAstNode.cs | 34 +++++ .../AvaloniaXamlIlLanguage.cs | 135 ++++++++++++++---- .../AvaloniaXamlIlWellKnownTypes.cs | 9 ++ 8 files changed, 192 insertions(+), 34 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index bfac1ac1e1..90f6abc873 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -1,7 +1,7 @@  netstandard2.0 - netstandard2.0;netcoreapp2.0 + netstandard2.0;netcoreapp3.1 exe false tools @@ -81,6 +81,15 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index d5c406293e..f8960f56ec 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -1,3 +1,4 @@ +#if !NETCOREAPP3_1 namespace System { // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory @@ -63,6 +64,8 @@ namespace System } public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); + + public static implicit operator ReadOnlySpan(char[] arr) => new ReadOnlySpan(new string(arr)); } static class SpanCompatExtensions @@ -71,3 +74,4 @@ namespace System } } +#endif diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index 57f308d59f..b8418949d9 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -8,7 +8,10 @@ namespace Avalonia.Controls /// /// Defines the valid units for a . /// - public enum GridUnitType +#if !BUILDTASK + public +#endif + enum GridUnitType { /// /// The row or column is auto-sized to fit its content. @@ -29,7 +32,10 @@ namespace Avalonia.Controls /// /// Holds the width or height of a 's column and row definitions. /// - public struct GridLength : IEquatable +#if !BUILDTASK + public +#endif + struct GridLength : IEquatable { private readonly GridUnitType _type; diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index 16b4f90d57..a57a962db4 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -1,17 +1,24 @@ using System; using System.Globalization; +#if !BUILDTASK using Avalonia.Animation.Animators; +#endif namespace Avalonia.Media { /// /// An ARGB color. /// - public readonly struct Color : IEquatable +#if !BUILDTASK + public +#endif + readonly struct Color : IEquatable { static Color() { +#if !BUILDTASK Animation.Animation.RegisterAnimator(prop => typeof(Color).IsAssignableFrom(prop.PropertyType)); +#endif } /// @@ -223,7 +230,12 @@ namespace Avalonia.Media if (input.Length == 3 || input.Length == 4) { var extendedLength = 2 * input.Length; + +#if !BUILDTASK Span extended = stackalloc char[extendedLength]; +#else + char[] extended = new char[extendedLength]; +#endif for (int i = 0; i < input.Length; i++) { diff --git a/src/Avalonia.Visuals/Media/KnownColors.cs b/src/Avalonia.Visuals/Media/KnownColors.cs index 0887d2c913..fe09f5f538 100644 --- a/src/Avalonia.Visuals/Media/KnownColors.cs +++ b/src/Avalonia.Visuals/Media/KnownColors.cs @@ -8,7 +8,9 @@ namespace Avalonia.Media { private static readonly IReadOnlyDictionary _knownColorNames; private static readonly IReadOnlyDictionary _knownColors; +#if !BUILDTASK private static readonly Dictionary _knownBrushes; +#endif static KnownColors() { @@ -32,14 +34,19 @@ namespace Avalonia.Media _knownColorNames = knownColorNames; _knownColors = knownColors; + +#if !BUILDTASK _knownBrushes = new Dictionary(); +#endif } +#if !BUILDTASK public static ISolidColorBrush GetKnownBrush(string s) { var color = GetKnownColor(s); return color != KnownColor.None ? color.ToBrush() : null; } +#endif public static KnownColor GetKnownColor(string s) { @@ -61,6 +68,7 @@ namespace Avalonia.Media return Color.FromUInt32((uint)color); } +#if !BUILDTASK public static ISolidColorBrush ToBrush(this KnownColor color) { lock (_knownBrushes) @@ -74,6 +82,7 @@ namespace Avalonia.Media return brush; } } +#endif } internal enum KnownColor : uint diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs new file mode 100644 index 0000000000..218c49512c --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs @@ -0,0 +1,34 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes +{ + class AvaloniaXamlIlGridLengthAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode + { + private readonly AvaloniaXamlIlWellKnownTypes _types; + private readonly GridLength _gridLength; + + public AvaloniaXamlIlGridLengthAstNode(IXamlLineInfo lineInfo, AvaloniaXamlIlWellKnownTypes types, GridLength gridLength) : base(lineInfo) + { + _types = types; + _gridLength = gridLength; + + Type = new XamlAstClrTypeReference(lineInfo, types.GridLength, false); + } + + public IXamlAstTypeReference Type { get; } + + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen + .Ldc_R8(_gridLength.Value) + .Ldc_I4((int)_gridLength.GridUnitType) + .Newobj(_types.GridLengthConstructorValueType); + + return XamlILNodeEmitResult.Type(0, Type.GetClrType()); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs index c3d9534828..87b82c112e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Avalonia.Controls; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using Avalonia.Media; using XamlX; using XamlX.Ast; using XamlX.Emit; @@ -205,67 +207,140 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions result = new AvaloniaXamlIlFontFamilyAstNode(types, text, node); return true; } - + if (type.Equals(types.Thickness)) { - var thickness = Thickness.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Thickness, types.ThicknessFullConstructor, - new[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom }); + try + { + var thickness = Thickness.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Thickness, types.ThicknessFullConstructor, + new[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node); + } } if (type.Equals(types.Point)) { - var point = Point.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor, - new[] { point.X, point.Y }); + try + { + var point = Point.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor, + new[] { point.X, point.Y }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node); + } } if (type.Equals(types.Vector)) { - var vector = Vector.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor, - new[] { vector.X, vector.Y }); + try + { + var vector = Vector.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor, + new[] { vector.X, vector.Y }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node); + } } if (type.Equals(types.Size)) { - var size = Size.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Size, types.SizeFullConstructor, - new[] { size.Width, size.Height }); + try + { + var size = Size.Parse(text); - return true; + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Size, types.SizeFullConstructor, + new[] { size.Width, size.Height }); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node); + } } if (type.Equals(types.Matrix)) { - var matrix = Matrix.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Matrix, types.MatrixFullConstructor, - new[] { matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31, matrix.M32 }); + try + { + var matrix = Matrix.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Matrix, types.MatrixFullConstructor, + new[] { matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31, matrix.M32 }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node); + } } if (type.Equals(types.CornerRadius)) { - var cornerRadius = CornerRadius.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.CornerRadius, types.CornerRadiusFullConstructor, - new[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft }); + try + { + var cornerRadius = CornerRadius.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.CornerRadius, types.CornerRadiusFullConstructor, + new[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft }); + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node); + } + } + + if (type.Equals(types.Color)) + { + if (!Color.TryParse(text, out Color color)) + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a color", node); + } + + result = new XamlStaticOrTargetedReturnMethodCallNode(node, + type.GetMethod( + new FindMethodMethodSignature("FromUInt32", type, types.UInt) { IsStatic = true }), + new[] { new XamlConstantNode(node, types.UInt, color.ToUint32()) }); + return true; } + if (type.Equals(types.GridLength)) + { + try + { + var gridLength = GridLength.Parse(text); + + result = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node); + } + } + if (type.FullName == "Avalonia.AvaloniaProperty") { var scope = context.ParentNodes().OfType().FirstOrDefault(); 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 05b13b61d3..2a7e10d42e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -49,6 +49,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ReflectionBindingExtension { get; } public IXamlType RelativeSource { get; } + public IXamlType UInt { get; } public IXamlType Long { get; } public IXamlType Uri { get; } public IXamlType FontFamily { get; } @@ -65,6 +66,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlConstructor MatrixFullConstructor { get; } public IXamlType CornerRadius { get; } public IXamlConstructor CornerRadiusFullConstructor { get; } + public IXamlType GridLength { get; } + public IXamlConstructor GridLengthConstructorValueType { get; } + public IXamlType Color { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -122,6 +126,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater"); ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension"); RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource"); + UInt = cfg.TypeSystem.GetType("System.UInt32"); Long = cfg.TypeSystem.GetType("System.Int64"); Uri = cfg.TypeSystem.GetType("System.Uri"); FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); @@ -141,6 +146,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers (Size, SizeFullConstructor) = GetNumericTypeInfo("Avalonia.Size", XamlIlTypes.Double, 2); (Matrix, MatrixFullConstructor) = GetNumericTypeInfo("Avalonia.Matrix", XamlIlTypes.Double, 6); (CornerRadius, CornerRadiusFullConstructor) = GetNumericTypeInfo("Avalonia.CornerRadius", XamlIlTypes.Double, 4); + + GridLength = cfg.TypeSystem.GetType("Avalonia.Controls.GridLength"); + GridLengthConstructorValueType = GridLength.GetConstructor(new List { XamlIlTypes.Double, cfg.TypeSystem.GetType("Avalonia.Controls.GridUnitType") }); + Color = cfg.TypeSystem.GetType("Avalonia.Media.Color"); } } From 823ab3a40ddd8f37bb3384f1a386d4f67ee13fc2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Nov 2020 18:21:07 +0800 Subject: [PATCH 09/33] address review --- src/Avalonia.Visuals/Media/PathFigure.cs | 8 ++++---- src/Avalonia.Visuals/Media/PathGeometry.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index a9e3c6d628..311a70a098 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -30,9 +30,9 @@ namespace Avalonia.Media internal event EventHandler SegmentsInvalidated; - private IDisposable? _segmentsObserver; + private IDisposable _segmentsDisposable; - private IDisposable? _segmentsPropertiesObserver; + private IDisposable _segmentsPropertiesObserver; /// /// Initializes a new instance of the class. @@ -50,10 +50,10 @@ namespace Avalonia.Media private void OnSegmentsChanged(PathSegments? arg2NewValue) { - _segmentsObserver?.Dispose(); + _segmentsDisposable?.Dispose(); _segmentsPropertiesObserver?.Dispose(); - _segmentsObserver = _segments?.ForEachItem( + _segmentsDisposable = _segments?.ForEachItem( _ => InvalidateSegments(), _ => InvalidateSegments(), InvalidateSegments); diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index 819669d86e..3d11c19b7d 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -120,7 +120,7 @@ namespace Avalonia.Media } - public void InvalidateGeometryFromSegments(object _, EventArgs __) + private void InvalidateGeometryFromSegments(object _, EventArgs __) { InvalidateGeometry(); } From 90e6e072212b1fb5924ba7f21027ab9a8dd993ac Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Nov 2020 18:23:28 +0800 Subject: [PATCH 10/33] address review --- src/Avalonia.Visuals/Media/PathFigure.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index 311a70a098..0c4cc592af 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -32,7 +32,7 @@ namespace Avalonia.Media private IDisposable _segmentsDisposable; - private IDisposable _segmentsPropertiesObserver; + private IDisposable _segmentsPropertiesDisposable; /// /// Initializes a new instance of the class. @@ -51,14 +51,14 @@ namespace Avalonia.Media private void OnSegmentsChanged(PathSegments? arg2NewValue) { _segmentsDisposable?.Dispose(); - _segmentsPropertiesObserver?.Dispose(); + _segmentsPropertiesDisposable?.Dispose(); _segmentsDisposable = _segments?.ForEachItem( _ => InvalidateSegments(), _ => InvalidateSegments(), InvalidateSegments); - _segmentsPropertiesObserver = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); + _segmentsPropertiesDisposable = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); } private void InvalidateSegments() From 538b723c2d25916bd1e1d2f0898a7923862d2281 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Nov 2020 20:53:07 +0800 Subject: [PATCH 11/33] address review --- src/Avalonia.Visuals/Media/PathFigure.cs | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index 0c4cc592af..2f8d11b06e 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,5 +1,5 @@ +#nullable enable using System; -using System.ComponentModel; using Avalonia.Collections; using Avalonia.Metadata; @@ -11,29 +11,35 @@ namespace Avalonia.Media /// Defines the property. /// public static readonly StyledProperty IsClosedProperty - = AvaloniaProperty.Register(nameof(IsClosed), true); + = AvaloniaProperty.Register(nameof(IsClosed), true); + /// /// Defines the property. /// public static readonly StyledProperty IsFilledProperty - = AvaloniaProperty.Register(nameof(IsFilled), true); + = AvaloniaProperty.Register(nameof(IsFilled), true); + /// /// Defines the property. /// public static readonly DirectProperty SegmentsProperty - = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, (f, s) => f.Segments = s); + = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, + (f, s) => f.Segments = s); + /// /// Defines the property. /// public static readonly StyledProperty StartPointProperty - = AvaloniaProperty.Register(nameof(StartPoint)); + = AvaloniaProperty.Register(nameof(StartPoint)); + + internal event EventHandler? SegmentsInvalidated; - internal event EventHandler SegmentsInvalidated; + private PathSegments? _segments; - private IDisposable _segmentsDisposable; + private IDisposable? _segmentsDisposable; + + private IDisposable? _segmentsPropertiesDisposable; - private IDisposable _segmentsPropertiesDisposable; - /// /// Initializes a new instance of the class. /// @@ -57,7 +63,7 @@ namespace Avalonia.Media _ => InvalidateSegments(), _ => InvalidateSegments(), InvalidateSegments); - + _segmentsPropertiesDisposable = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); } @@ -127,8 +133,6 @@ namespace Avalonia.Media ctx.EndFigure(IsClosed); } - private PathSegments _segments; - public override string ToString() => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } From 1800ad01257216c30ae5c40b8567ab437a0432f2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Nov 2020 13:57:16 +0000 Subject: [PATCH 12/33] fix nullable warnings. --- src/Avalonia.Visuals/Media/PathFigure.cs | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index 2f8d11b06e..caf86cb234 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Linq; using Avalonia.Collections; using Avalonia.Metadata; @@ -22,8 +23,10 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly DirectProperty SegmentsProperty - = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, + public static readonly DirectProperty SegmentsProperty + = AvaloniaProperty.RegisterDirect( + nameof(Segments), + f => f.Segments, (f, s) => f.Segments = s); /// @@ -50,11 +53,12 @@ namespace Avalonia.Media static PathFigure() { - SegmentsProperty.Changed.AddClassHandler((s, e) => - s.OnSegmentsChanged(e.NewValue as PathSegments)); + SegmentsProperty.Changed.AddClassHandler( + (s, e) => + s.OnSegmentsChanged()); } - private void OnSegmentsChanged(PathSegments? arg2NewValue) + private void OnSegmentsChanged() { _segmentsDisposable?.Dispose(); _segmentsPropertiesDisposable?.Dispose(); @@ -103,7 +107,7 @@ namespace Avalonia.Media /// The segments. /// [Content] - public PathSegments Segments + public PathSegments? Segments { get { return _segments; } set { SetAndRaise(SegmentsProperty, ref _segments, value); } @@ -120,20 +124,23 @@ namespace Avalonia.Media get { return GetValue(StartPointProperty); } set { SetValue(StartPointProperty, value); } } + + public override string ToString() + => $"M {StartPoint} {string.Join(" ", _segments ?? Enumerable.Empty())}{(IsClosed ? "Z" : "")}"; internal void ApplyTo(StreamGeometryContext ctx) { ctx.BeginFigure(StartPoint, IsFilled); - foreach (var segment in Segments) + if (Segments != null) { - segment.ApplyTo(ctx); + foreach (var segment in Segments) + { + segment.ApplyTo(ctx); + } } ctx.EndFigure(IsClosed); } - - public override string ToString() - => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } } From dc698b3b1d404a92aebdf7183c13fe9769fcc116 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 19 Nov 2020 12:26:11 +0100 Subject: [PATCH 13/33] Reuse empty value store arrays. --- .../Utilities/AvaloniaPropertyValueStore.cs | 24 +++++++++++++++---- .../Layout/ControlsBenchmark.cs | 3 ++- .../Avalonia.Benchmarks/NullCursorFactory.cs | 14 +++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/NullCursorFactory.cs diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs index 0238446892..6e52b6770a 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace Avalonia.Utilities { @@ -9,12 +12,14 @@ namespace Avalonia.Utilities /// Stored value type. internal sealed class AvaloniaPropertyValueStore { + // The last item in the list is always int.MaxValue. + private static readonly Entry[] s_emptyEntries = { new Entry { PropertyId = int.MaxValue, Value = default! } }; + private Entry[] _entries; public AvaloniaPropertyValueStore() { - // The last item in the list is always int.MaxValue - _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = default } }; + _entries = s_emptyEntries; } private (int, bool) TryFindEntry(int propertyId) @@ -86,7 +91,7 @@ namespace Avalonia.Utilities return (0, false); } - public bool TryGetValue(AvaloniaProperty property, out TValue value) + public bool TryGetValue(AvaloniaProperty property, [MaybeNull] out TValue value) { (int index, bool found) = TryFindEntry(property.Id); if (!found) @@ -132,7 +137,18 @@ namespace Avalonia.Utilities if (found) { - Entry[] entries = new Entry[_entries.Length - 1]; + var newLength = _entries.Length - 1; + + // Special case - one element left means that value store is empty so we can just reuse our "empty" array. + if (newLength == 1) + { + _entries = s_emptyEntries; + + return; + } + + var entries = new Entry[newLength]; + int ix = 0; for (int i = 0; i < _entries.Length; ++i) diff --git a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs index 7170f6d7d4..3493dd0f53 100644 --- a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs @@ -17,7 +17,8 @@ namespace Avalonia.Benchmarks.Layout _app = UnitTestApplication.Start( TestServices.StyledWindow.With( renderInterface: new NullRenderingPlatform(), - threadingInterface: new NullThreadingPlatform())); + threadingInterface: new NullThreadingPlatform(), + standardCursorFactory: new NullCursorFactory())); _root = new TestRoot(true, null) { diff --git a/tests/Avalonia.Benchmarks/NullCursorFactory.cs b/tests/Avalonia.Benchmarks/NullCursorFactory.cs new file mode 100644 index 0000000000..012adce0f2 --- /dev/null +++ b/tests/Avalonia.Benchmarks/NullCursorFactory.cs @@ -0,0 +1,14 @@ +using System; +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.Benchmarks +{ + internal class NullCursorFactory : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + return new PlatformHandle(IntPtr.Zero, "null"); + } + } +} From 492534fc9a2239009baf7029b954b215c8526f33 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 19 Nov 2020 12:59:05 +0100 Subject: [PATCH 14/33] Parse cursor type during xaml compilation. --- .../CompilerExtensions/AvaloniaXamlIlLanguage.cs | 12 ++++++++++++ .../Transformers/AvaloniaXamlIlWellKnownTypes.cs | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs index 87b82c112e..40cfd21a76 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -341,6 +341,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + if (type.Equals(types.Cursor)) + { + if (TypeSystemHelpers.TryGetEnumValueNode(types.StandardCursorType, text, node, out var enumConstantNode)) + { + var cursorTypeRef = new XamlAstClrTypeReference(node, types.Cursor, false); + + result = new XamlAstNewClrObjectNode(node, cursorTypeRef, types.CursorTypeConstructor, new List { enumConstantNode }); + + return true; + } + } + if (type.FullName == "Avalonia.AvaloniaProperty") { var scope = context.ParentNodes().OfType().FirstOrDefault(); 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 2a7e10d42e..f046778429 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -69,6 +69,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType GridLength { get; } public IXamlConstructor GridLengthConstructorValueType { get; } public IXamlType Color { get; } + public IXamlType StandardCursorType { get; } + public IXamlType Cursor { get; } + public IXamlConstructor CursorTypeConstructor { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -150,6 +153,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers GridLength = cfg.TypeSystem.GetType("Avalonia.Controls.GridLength"); GridLengthConstructorValueType = GridLength.GetConstructor(new List { XamlIlTypes.Double, cfg.TypeSystem.GetType("Avalonia.Controls.GridUnitType") }); Color = cfg.TypeSystem.GetType("Avalonia.Media.Color"); + StandardCursorType = cfg.TypeSystem.GetType("Avalonia.Input.StandardCursorType"); + Cursor = cfg.TypeSystem.GetType("Avalonia.Input.Cursor"); + CursorTypeConstructor = Cursor.GetConstructor(new List { StandardCursorType }); } } From eb0389e95635d405b104fe50dd8e53b15182dfed Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 19 Nov 2020 18:51:30 +0000 Subject: [PATCH 15/33] remove toggleswitch minwidth. --- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 2 +- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 9d1c024eb9..f11f77df5a 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -5,7 +5,7 @@ 0,0,0,6 6 6 - 154 + 0 0 1 diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 2491225a44..45537e41ad 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -5,7 +5,7 @@ 0,0,0,6 6 6 - 154 + 0 From 165bc2840909cda8c0c6fe751974757b4f32e84c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 21 Nov 2020 19:19:27 +0300 Subject: [PATCH 16/33] [AVNCOM] Manually pass `bool` values as `int` for now --- src/Avalonia.Native/Extensions.cs | 8 ++++++++ src/Avalonia.Native/IAvnMenuItem.cs | 2 +- src/Avalonia.Native/PlatformThreadingInterface.cs | 6 +++--- src/Avalonia.Native/PopupImpl.cs | 4 ++-- src/Avalonia.Native/PredicateCallback.cs | 4 ++-- src/Avalonia.Native/ScreenImpl.cs | 2 +- src/Avalonia.Native/SystemDialogs.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 12 ++++++------ src/Avalonia.Native/WindowImplBase.cs | 12 ++++++------ src/Avalonia.Native/avn.idl | 1 + 10 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 src/Avalonia.Native/Extensions.cs diff --git a/src/Avalonia.Native/Extensions.cs b/src/Avalonia.Native/Extensions.cs new file mode 100644 index 0000000000..c0d90f7a85 --- /dev/null +++ b/src/Avalonia.Native/Extensions.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Native +{ + internal static class Extensions + { + public static int AsComBool(this bool b) => b ? 1 : 0; + public static bool FromComBool(this int b) => b != 0; + } +} diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs index e2feffaa33..4c0c81394f 100644 --- a/src/Avalonia.Native/IAvnMenuItem.cs +++ b/src/Avalonia.Native/IAvnMenuItem.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native.Interop.Impl private void UpdateTitle(string title) => SetTitle(title ?? ""); - private void UpdateIsChecked(bool isChecked) => SetIsChecked(isChecked); + private void UpdateIsChecked(bool isChecked) => SetIsChecked(isChecked.AsComBool()); private void UpdateToggleType(NativeMenuItemToggleType toggleType) { diff --git a/src/Avalonia.Native/PlatformThreadingInterface.cs b/src/Avalonia.Native/PlatformThreadingInterface.cs index df69f2eafb..882d5a2ed3 100644 --- a/src/Avalonia.Native/PlatformThreadingInterface.cs +++ b/src/Avalonia.Native/PlatformThreadingInterface.cs @@ -33,9 +33,9 @@ namespace Avalonia.Native _parent = parent; } - public void Signaled(int priority, bool priorityContainsMeaningfulValue) + public void Signaled(int priority, int priorityContainsMeaningfulValue) { - _parent.Signaled?.Invoke(priorityContainsMeaningfulValue ? (DispatcherPriority?)priority : null); + _parent.Signaled?.Invoke(priorityContainsMeaningfulValue.FromComBool() ? (DispatcherPriority?)priority : null); } } @@ -50,7 +50,7 @@ namespace Avalonia.Native _native.SetSignaledCallback(cb); } - public bool CurrentThreadIsLoopThread => _native.CurrentThreadIsLoopThread; + public bool CurrentThreadIsLoopThread => _native.CurrentThreadIsLoopThread.FromComBool(); public event Action Signaled; diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 2f98385038..60c552a937 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -50,9 +50,9 @@ namespace Avalonia.Native // NOP on Popup } - bool IAvnWindowEvents.Closing() + int IAvnWindowEvents.Closing() { - return true; + return true.AsComBool(); } void IAvnWindowEvents.WindowStateChanged(AvnWindowState state) diff --git a/src/Avalonia.Native/PredicateCallback.cs b/src/Avalonia.Native/PredicateCallback.cs index 1ed2ae36af..19c470bcb3 100644 --- a/src/Avalonia.Native/PredicateCallback.cs +++ b/src/Avalonia.Native/PredicateCallback.cs @@ -12,9 +12,9 @@ namespace Avalonia.Native _predicate = predicate; } - bool IAvnPredicateCallback.Evaluate() + int IAvnPredicateCallback.Evaluate() { - return _predicate(); + return _predicate().AsComBool(); } } } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index ae6da01388..7b4a001486 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.Native screen.PixelDensity, screen.Bounds.ToAvaloniaPixelRect(), screen.WorkingArea.ToAvaloniaPixelRect(), - screen.Primary); + screen.Primary.FromComBool()); } return result; diff --git a/src/Avalonia.Native/SystemDialogs.cs b/src/Avalonia.Native/SystemDialogs.cs index 0239fc680d..8012813644 100644 --- a/src/Avalonia.Native/SystemDialogs.cs +++ b/src/Avalonia.Native/SystemDialogs.cs @@ -26,7 +26,7 @@ namespace Avalonia.Native if (dialog is OpenFileDialog ofd) { _native.OpenFileDialog(nativeParent, - events, ofd.AllowMultiple, + events, ofd.AllowMultiple.AsComBool(), ofd.Title ?? "", ofd.Directory ?? "", ofd.InitialFileName ?? "", diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index b42831854d..f60e83efe3 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -42,14 +42,14 @@ namespace Avalonia.Native _parent = parent; } - bool IAvnWindowEvents.Closing() + int IAvnWindowEvents.Closing() { if (_parent.Closing != null) { - return _parent.Closing(); + return _parent.Closing().AsComBool(); } - return true; + return true.AsComBool(); } void IAvnWindowEvents.WindowStateChanged(AvnWindowState state) @@ -69,7 +69,7 @@ namespace Avalonia.Native public void CanResize(bool value) { - _native.SetCanResize(value); + _native.SetCanResize(value.AsComBool()); } public void SetSystemDecorations(Controls.SystemDecorations enabled) @@ -145,7 +145,7 @@ namespace Avalonia.Native { _isExtended = extendIntoClientAreaHint; - _native.SetExtendClientArea(extendIntoClientAreaHint); + _native.SetExtendClientArea(extendIntoClientAreaHint.AsComBool()); InvalidateExtendedMargins(); } @@ -198,7 +198,7 @@ namespace Avalonia.Native public void SetEnabled(bool enable) { - _native.SetEnabled(enable); + _native.SetEnabled(enable.AsComBool()); } } } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 150ab2703e..88c3144884 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -192,14 +192,14 @@ namespace Avalonia.Native _parent.RawMouseEvent(type, timeStamp, modifiers, point, delta); } - bool IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) + int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) { - return _parent.RawKeyEvent(type, timeStamp, modifiers, key); + return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool(); } - bool IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text) + int IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text) { - return _parent.RawTextInputEvent(timeStamp, text); + return _parent.RawTextInputEvent(timeStamp, text).AsComBool(); } @@ -388,7 +388,7 @@ namespace Avalonia.Native public void SetTopmost(bool value) { - _native.SetTopMost(value); + _native.SetTopMost(value.AsComBool()); } public double RenderScaling => _native?.Scaling ?? 1; @@ -456,7 +456,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled((TransparencyLevel >= WindowTransparencyLevel.Blur).AsComBool()); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 1d36cce20d..3f33476c3d 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -1,5 +1,6 @@ @clr-namespace Avalonia.Native.Interop @clr-access internal +@clr-map bool int @cpp-preamble @@ #include "com.h" #include "key.h" From b9a9ac94e76b0e75916539a355f4609bfff6824b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 21 Nov 2020 16:59:35 +0000 Subject: [PATCH 17/33] Fix mainthread detection osx. --- native/Avalonia.Native/src/OSX/platformthreading.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index f93436d157..e83bf53331 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -101,7 +101,7 @@ public: virtual bool GetCurrentThreadIsLoopThread() override { - return [[NSThread currentThread] isMainThread]; + return [NSThread isMainThread]; } virtual void SetSignaledCallback(IAvnSignaledCallback* cb) override { From 89b5d13408cca05ec939474d78a8499b6ec4fa8c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 21 Nov 2020 23:01:57 +0000 Subject: [PATCH 18/33] [OSX] remove most of the stretching and flickering by calling updateLayer at the end of resize. --- native/Avalonia.Native/src/OSX/rendertarget.mm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 93a33bbbb0..6643c5c089 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -143,13 +143,17 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta return _layer; } -- (void)resize:(AvnPixelSize)size withScale: (float) scale;{ +- (void)resize:(AvnPixelSize)size withScale: (float) scale{ @synchronized (lock) { if(surface == nil || surface->size.Width != size.Width || surface->size.Height != size.Height || surface->scale != scale) + { surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()]; + + [self updateLayer]; + } } } From d96bfe6996d7c8a3f42186f907134c4952f20466 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 21 Nov 2020 23:02:32 +0000 Subject: [PATCH 19/33] Remove last bit of flickering by wrapping layer updates inside a CATransaction. --- native/Avalonia.Native/src/OSX/rendertarget.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 6643c5c089..00b6dab219 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -2,6 +2,7 @@ #include "rendertarget.h" #import #import +#import #include #include @@ -163,12 +164,15 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta @synchronized (lock) { if(_layer == nil) return; + [CATransaction begin]; [_layer setContents: nil]; if(surface != nil) { [_layer setContentsScale: surface->scale]; [_layer setContents: (__bridge IOSurface*) surface->surface]; } + [CATransaction commit]; + [CATransaction flush]; } } else From 019a5096f1372c4dee801befbd654bff0f1c72fe Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 22 Nov 2020 12:27:28 +0000 Subject: [PATCH 20/33] incremement min win10 version for win.ui.comp. --- .../WinRT/Composition/WinUICompositorConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 7e1f97ab84..2aa82436f6 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -134,7 +134,7 @@ namespace Avalonia.Win32.WinRT.Composition public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle) { const int majorRequired = 10; - const int buildRequired = 16299; + const int buildRequired = 17134; var majorInstalled = Win32Platform.WindowsVersion.Major; var buildInstalled = Win32Platform.WindowsVersion.Build; From 31bfcb6b05262cad46fce35340932b742e1b88e6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 11:39:52 +0100 Subject: [PATCH 21/33] Fix BindingValue comparison. `NativeMenuBar` click forwarding was broken by https://github.com/AvaloniaUI/Avalonia/pull/4648 - this fixes it. --- src/Avalonia.Controls/NativeMenuBar.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index 63bb39108f..5d2e1087a7 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -1,7 +1,6 @@ using System; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; -using Avalonia.Styling; namespace Avalonia.Controls { @@ -16,7 +15,7 @@ namespace Avalonia.Controls EnableMenuItemClickForwardingProperty.Changed.Subscribe(args => { var item = (MenuItem)args.Sender; - if (args.NewValue.Equals(true)) + if (args.NewValue.GetValueOrDefault()) item.Click += OnMenuItemClick; else item.Click -= OnMenuItemClick; From 5d4b56d04207025a4e161606ceac6a34b11bfd8b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 12:18:16 +0100 Subject: [PATCH 22/33] NativeMenuItem.Clicked -> Click. And mark `Clicked` as obsolete. For consistency with `MenuItem.Click`. --- src/Avalonia.Controls/NativeMenuItem.cs | 16 +++++++++++++--- .../AvaloniaNativeMenuExporter.cs | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index a0fec9e677..e8cb59e76e 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -151,7 +151,7 @@ namespace Avalonia.Controls IsEnabled = _command?.CanExecute(null) ?? true; } - public bool HasClickHandlers => Clicked != null; + public bool HasClickHandlers => Click != null; public ICommand Command { @@ -182,11 +182,21 @@ namespace Avalonia.Controls set { SetValue(CommandParameterProperty, value); } } - public event EventHandler Clicked; + /// + /// Occurs when a is clicked. + /// + public event EventHandler Click; + + [Obsolete("Use Click event.")] + public event EventHandler Clicked + { + add => Click += value; + remove => Click -= value; + } void INativeMenuItemExporterEventsImplBridge.RaiseClicked() { - Clicked?.Invoke(this, new EventArgs()); + Click?.Invoke(this, new EventArgs()); if (Command?.CanExecute(CommandParameter) == true) { diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index b192de95de..2e3408eca5 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -60,7 +60,7 @@ namespace Avalonia.Native Header = "About Avalonia", }; - aboutItem.Clicked += async (sender, e) => + aboutItem.Click += async (sender, e) => { var dialog = new AboutAvaloniaDialog(); From e59573d6309bb98e75ccd75d9c3c8050a9de4d7a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 12:33:17 +0100 Subject: [PATCH 23/33] Mark NativeMenuItem.Menu as content property. Means that submenus don't need the extra `` element. --- samples/ControlCatalog/DecoratedWindow.xaml | 30 ++++------ samples/ControlCatalog/MainWindow.xaml | 66 +++++++++------------ src/Avalonia.Controls/NativeMenuItem.cs | 2 + 3 files changed, 44 insertions(+), 54 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 8e4c97b7f0..5251a2fa55 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -6,25 +6,21 @@ - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 97bd88f5e4..6a70bb082f 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -16,47 +16,39 @@ - - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + - - - - - - - + + + + + diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index e8cb59e76e..76197b62ad 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -2,6 +2,7 @@ using System; using System.Windows.Input; using Avalonia.Input; using Avalonia.Media.Imaging; +using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Controls @@ -62,6 +63,7 @@ namespace Avalonia.Controls public static readonly DirectProperty MenuProperty = AvaloniaProperty.RegisterDirect(nameof(Menu), o => o.Menu, (o, v) => o.Menu = v); + [Content] public NativeMenu Menu { get => _menu; From ad677f0ff43ca6bcd9f5f5524692e3d58a6956c6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 15:28:42 +0100 Subject: [PATCH 24/33] Added failing test for #4900. --- .../Xaml/ShapeTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs new file mode 100644 index 0000000000..bd577e84f8 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs @@ -0,0 +1,24 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class ShapeTests : XamlTestBase + { + [Fact] + public void Can_Specify_DashStyle_In_XAML() + { + var xaml = @" + + + + +"; + + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); + + Assert.NotNull(target); + } + } +} From d99512a785e78c5976a2f70f013a21dd40743342 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 15:29:37 +0100 Subject: [PATCH 25/33] Make DashStyle.Dashes an AvaloniaList. So that it can be parsed from XAML and changes can be made. --- src/Avalonia.Visuals/Media/DashStyle.cs | 75 +++++++++++++------ .../Media/PenTests.cs | 18 ++++- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index 1e813edc13..acba2d3615 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -1,11 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using Avalonia.Animation; +using Avalonia.Collections; +using Avalonia.Media.Immutable; + +#nullable enable + namespace Avalonia.Media { - using System; - using System.Collections.Generic; - using System.Linq; - using Avalonia.Animation; - using Avalonia.Media.Immutable; - /// /// Represents the sequence of dashes and gaps that will be applied by a . /// @@ -14,8 +17,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> DashesProperty = - AvaloniaProperty.Register>(nameof(Dashes)); + public static readonly StyledProperty> DashesProperty = + AvaloniaProperty.Register>(nameof(Dashes)); /// /// Defines the property. @@ -23,10 +26,10 @@ namespace Avalonia.Media public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register(nameof(Offset)); - private static ImmutableDashStyle s_dash; - private static ImmutableDashStyle s_dot; - private static ImmutableDashStyle s_dashDot; - private static ImmutableDashStyle s_dashDotDot; + private static ImmutableDashStyle? s_dash; + private static ImmutableDashStyle? s_dot; + private static ImmutableDashStyle? s_dashDot; + private static ImmutableDashStyle? s_dashDotDot; /// /// Initializes a new instance of the class. @@ -41,9 +44,9 @@ namespace Avalonia.Media /// /// The dashes collection. /// The dash sequence offset. - public DashStyle(IEnumerable dashes, double offset) + public DashStyle(IEnumerable? dashes, double offset) { - Dashes = (IReadOnlyList)dashes?.ToList() ?? Array.Empty(); + Dashes = (dashes as AvaloniaList) ?? new AvaloniaList(dashes ?? Array.Empty()); Offset = offset; } @@ -61,31 +64,27 @@ namespace Avalonia.Media /// /// Represents a dashed . /// - public static IDashStyle Dash => - s_dash ?? (s_dash = new ImmutableDashStyle(new double[] { 2, 2 }, 1)); + public static IDashStyle Dash => s_dash ??= new ImmutableDashStyle(new double[] { 2, 2 }, 1); /// /// Represents a dotted . /// - public static IDashStyle Dot => - s_dot ?? (s_dot = new ImmutableDashStyle(new double[] { 0, 2 }, 0)); + public static IDashStyle Dot => s_dot ??= new ImmutableDashStyle(new double[] { 0, 2 }, 0); /// /// Represents a dashed dotted . /// - public static IDashStyle DashDot => - s_dashDot ?? (s_dashDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2 }, 1)); + public static IDashStyle DashDot => s_dashDot ??= new ImmutableDashStyle(new double[] { 2, 2, 0, 2 }, 1); /// /// Represents a dashed double dotted . /// - public static IDashStyle DashDotDot => - s_dashDotDot ?? (s_dashDotDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1)); + public static IDashStyle DashDotDot => s_dashDotDot ??= new ImmutableDashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1); /// /// Gets or sets the length of alternating dashes and gaps. /// - public IReadOnlyList Dashes + public AvaloniaList Dashes { get => GetValue(DashesProperty); set => SetValue(DashesProperty, value); @@ -100,15 +99,43 @@ namespace Avalonia.Media set => SetValue(OffsetProperty, value); } + IReadOnlyList IDashStyle.Dashes => Dashes; + /// /// Raised when the dash style changes. /// - public event EventHandler Invalidated; + public event EventHandler? Invalidated; /// /// Returns an immutable clone of the . /// /// public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == DashesProperty) + { + var oldValue = change.OldValue.GetValueOrDefault>(); + var newValue = change.NewValue.GetValueOrDefault>(); + + if (oldValue is object) + { + oldValue.CollectionChanged -= DashesChanged; + } + + if (newValue is object) + { + newValue.CollectionChanged += DashesChanged; + } + } + } + + private void DashesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Invalidated?.Invoke(this, e); + } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index 418ac7576b..c2a1a5f9e4 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -1,4 +1,5 @@ -using Avalonia.Media; +using Avalonia.Collections; +using Avalonia.Media; using Avalonia.Media.Immutable; using Xunit; @@ -39,7 +40,20 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.Invalidated += (s, e) => raised = true; - dashes.Dashes = new[] { 0.1, 0.2 }; + dashes.Dashes = new AvaloniaList { 0.1, 0.2 }; + + Assert.True(raised); + } + + [Fact] + public void Adding_DashStyle_Dashes_Raises_Invalidated() + { + var dashes = new DashStyle(); + var target = new Pen { DashStyle = dashes }; + var raised = false; + + target.Invalidated += (s, e) => raised = true; + dashes.Dashes.Add(0.3); Assert.True(raised); } From 6e41b197f0b9513ba264701f39d8ce1b7436a841 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 15:35:04 +0100 Subject: [PATCH 26/33] Updated API compat. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 42 +++------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 2d00d82a46..62f4def701 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,39 +1,5 @@ Compat issues with assembly Avalonia.Visuals: -MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.Geometry.GetRenderBounds(Avalonia.Media.Pen)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public System.Boolean Avalonia.Media.Geometry.StrokeContains(Avalonia.Media.Pen, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Media.GlyphRunDrawing.BaselineOrigin.set(Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. -TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract. -CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract. -CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract. -TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.DrawableTextRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size.get()' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.ShapedTextCharacters.Bounds.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextCharacters.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract. -CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IDrawingContextLayerImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. -MembersMustExist : Member 'public Avalonia.Utilities.IRef Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. -Total Issues: 37 +MembersMustExist : Member 'public Avalonia.StyledProperty> Avalonia.StyledProperty> Avalonia.Media.DashStyle.DashesProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Collections.Generic.IReadOnlyList Avalonia.Media.DashStyle.Dashes.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.DashStyle.Dashes.set(System.Collections.Generic.IReadOnlyList)' does not exist in the implementation but it does exist in the contract. +Total Issues: 3 From 90fb76f364605928ef124e9e875066a4bcc10a60 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 15:50:11 +0100 Subject: [PATCH 27/33] Prevent exceptions when removing styles. When `_target.Owner` was null, the resource observable produced a `null` which may not be valid for the target property, resulting in an exception . Although the exception is caught, it slows things down. --- src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs index 250274c39b..fedd3469e6 100644 --- a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs @@ -147,13 +147,16 @@ namespace Avalonia.Controls { if (_target.Owner is object) { - observer.OnNext(Convert(_target.Owner?.FindResource(_key))); + observer.OnNext(Convert(_target.Owner.FindResource(_key))); } } private void PublishNext() { - PublishNext(Convert(_target.Owner?.FindResource(_key))); + if (_target.Owner is object) + { + PublishNext(Convert(_target.Owner.FindResource(_key))); + } } private void OwnerChanged(object sender, EventArgs e) From 4aa013a79e67e4152ec57b93ce4b1cbfc339212c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 16:25:07 +0100 Subject: [PATCH 28/33] Don't handle tab navigation in DefaultMenuInteractionHandler. Allow the default keyboard interaction handler to handle tab key presses in menus, because the logic in `DefaultMenuInteractionHandler` was causing the focus to get stuck in the menu. Also fix `NavigationDirection.IsDirectional` method. Fixes #5002. --- src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs | 2 +- src/Avalonia.Input/NavigationDirection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index a54d1ce308..5cbd3698b6 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls.Platform { var direction = e.Key.ToNavigationDirection(); - if (direction.HasValue) + if (direction?.IsDirectional() == true) { if (item == null && _isContextMenu) { diff --git a/src/Avalonia.Input/NavigationDirection.cs b/src/Avalonia.Input/NavigationDirection.cs index 9b9af0b0a6..9bd6a8bb42 100644 --- a/src/Avalonia.Input/NavigationDirection.cs +++ b/src/Avalonia.Input/NavigationDirection.cs @@ -83,7 +83,7 @@ namespace Avalonia.Input /// public static bool IsDirectional(this NavigationDirection direction) { - return direction > NavigationDirection.Previous || + return direction > NavigationDirection.Previous && direction <= NavigationDirection.PageDown; } From 4ab5cc4bbdf0dda18db79db1d389867fa6f36d41 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 23 Nov 2020 16:24:28 +0000 Subject: [PATCH 29/33] enable datavalidation on slider value property. --- src/Avalonia.Controls/Slider.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 6e08e78813..fe4b24099f 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -3,6 +3,7 @@ using Avalonia.Collections; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; @@ -94,6 +95,8 @@ namespace Avalonia.Controls Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); + + ValueProperty.OverrideMetadata(new DirectPropertyMetadata(enableDataValidation: true)); } /// @@ -225,6 +228,14 @@ namespace Avalonia.Controls Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue; } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) + { + if (property == ValueProperty) + { + DataValidationErrors.SetError(this, value.Error); + } + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); From b16820a230e566ed7606cdc7ed3a302f1f88c53e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 23:12:09 +0100 Subject: [PATCH 30/33] Added failing test for application resources. `DynamicResource` in `Application.Resources` fails to resolve resource. --- .../DynamicResourceExtensionTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 47a73cb360..9152131ab3 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -345,6 +345,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + [Fact] + public void DynamicResource_Can_Be_Assigned_To_Resource_Property_In_Application() + { + var xaml = @" + + + #ff506070 + + +"; + + var application = (Application)AvaloniaRuntimeXamlLoader.Load(xaml); + var brush = (SolidColorBrush)application.Resources["brush"]; + + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } [Fact] public void DynamicResource_Can_Be_Assigned_To_ItemTemplate_Property() From 64677edb8a50896baa9b15d1dbb81343c041da68 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 23:17:43 +0100 Subject: [PATCH 31/33] Fix resolving dynamic resource in Application. Instead of looking for an `IStyledElement` as an anchor, look for an `IResourceHost` because `Application` doesn't implement `IStyledElement` but it does implement `IResourceHost` and this interface has everything we need. --- .../Controls/ResourceNodeExtensions.cs | 6 +++--- .../DynamicResourceExtension.cs | 18 +++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs index 250274c39b..9654d5d9e3 100644 --- a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls } public static IObservable GetResourceObservable( - this IStyledElement control, + this IResourceHost control, object key, Func? converter = null) { @@ -83,11 +83,11 @@ namespace Avalonia.Controls private class ResourceObservable : LightweightObservableBase { - private readonly IStyledElement _target; + private readonly IResourceHost _target; private readonly object _key; private readonly Func? _converter; - public ResourceObservable(IStyledElement target, object key, Func? converter) + public ResourceObservable(IResourceHost target, object key, Func? converter) { _target = target; _key = key; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 03fd2e60dd..95380be65f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -10,8 +10,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { public class DynamicResourceExtension : IBinding { - private IStyledElement? _anchor; - private IResourceProvider? _resourceProvider; + private object? _anchor; public DynamicResourceExtension() { @@ -30,12 +29,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions if (!(provideTarget.TargetObject is IStyledElement)) { - _anchor = serviceProvider.GetFirstParent(); - - if (_anchor is null) - { - _resourceProvider = serviceProvider.GetFirstParent(); - } + _anchor = serviceProvider.GetFirstParent() ?? + serviceProvider.GetFirstParent() ?? + (object?)serviceProvider.GetFirstParent(); } return this; @@ -52,16 +48,16 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions return null; } - var control = target as IStyledElement ?? _anchor as IStyledElement; + var control = target as IResourceHost ?? _anchor as IResourceHost; if (control != null) { var source = control.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); return InstancedBinding.OneWay(source); } - else if (_resourceProvider is object) + else if (_anchor is IResourceProvider resourceProvider) { - var source = _resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); + var source = resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); return InstancedBinding.OneWay(source); } From b989086c5f242feb1f06eb9cdc36de7be31eaf64 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Nov 2020 23:27:16 +0100 Subject: [PATCH 32/33] Add breaking change. --- src/Avalonia.Styling/ApiCompatBaseline.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/Avalonia.Styling/ApiCompatBaseline.txt diff --git a/src/Avalonia.Styling/ApiCompatBaseline.txt b/src/Avalonia.Styling/ApiCompatBaseline.txt new file mode 100644 index 0000000000..001e165830 --- /dev/null +++ b/src/Avalonia.Styling/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Styling: +MembersMustExist : Member 'public System.IObservable Avalonia.Controls.ResourceNodeExtensions.GetResourceObservable(Avalonia.IStyledElement, System.Object, System.Func)' does not exist in the implementation but it does exist in the contract. +Total Issues: 1 From b488e882d448e4d758a609633ba334d4ac539fe7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 24 Nov 2020 14:14:39 +0000 Subject: [PATCH 33/33] Add a background to PathIcon. --- src/Avalonia.Themes.Default/PathIcon.xaml | 15 +++++++++------ src/Avalonia.Themes.Fluent/PathIcon.xaml | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Themes.Default/PathIcon.xaml b/src/Avalonia.Themes.Default/PathIcon.xaml index a2d01f7b5b..039f0ef10c 100644 --- a/src/Avalonia.Themes.Default/PathIcon.xaml +++ b/src/Avalonia.Themes.Default/PathIcon.xaml @@ -2,16 +2,19 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/src/Avalonia.Themes.Fluent/PathIcon.xaml b/src/Avalonia.Themes.Fluent/PathIcon.xaml index 0a2c747514..d4952b3571 100644 --- a/src/Avalonia.Themes.Fluent/PathIcon.xaml +++ b/src/Avalonia.Themes.Fluent/PathIcon.xaml @@ -10,16 +10,19 @@