From 6a1dbaea6587a7800180cb1986916b1a591b2155 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 19:23:15 +0100 Subject: [PATCH 01/39] initial implementation for skia. --- .../Media/Imaging/WriteableBitmap.cs | 45 +++++++++++++ .../Platform/IPlatformRenderInterface.cs | 32 +++++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 25 +++++++ src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 65 +++++++++++++++++++ 4 files changed, 167 insertions(+) diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index b86444bc2f..d10c743894 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -1,5 +1,8 @@ using System; +using System.IO; +using System.Threading.Tasks; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media.Imaging { @@ -34,8 +37,50 @@ namespace Avalonia.Media.Imaging { } + private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl) + { + + } + public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock(); + public static WriteableBitmap Decode(Stream stream) + { + var ri = AvaloniaLocator.Current.GetService(); + + return new WriteableBitmap(ri.LoadWriteableBitmap(stream)); + } + + /// + /// Loads a WriteableBitmap from a stream and decodes at the desired width. Aspect ratio is maintained. + /// This is more efficient than loading and then resizing. + /// + /// The stream to read the bitmap from. This can be any supported image format. + /// The desired width of the resulting bitmap. + /// The to use should any scaling be required. + /// An instance of the class. + public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + var ri = AvaloniaLocator.Current.GetService(); + + return new WriteableBitmap(ri.LoadWriteableBitmapToWidth(stream, width, interpolationMode)); + } + + /// + /// Loads a Bitmap from a stream and decodes at the desired height. Aspect ratio is maintained. + /// This is more efficient than loading and then resizing. + /// + /// The stream to read the bitmap from. This can be any supported image format. + /// The desired height of the resulting bitmap. + /// The to use should any scaling be required. + /// An instance of the class. + public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + var ri = AvaloniaLocator.Current.GetService(); + + return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode)); + } + private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat) { var ri = AvaloniaLocator.Current.GetService(); diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index fb4a4427b7..c141f2024f 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -100,6 +100,38 @@ namespace Avalonia.Platform /// An . IBitmapImpl LoadBitmap(Stream stream); + /// + /// Loads a WriteableBitmap implementation from a stream to a specified width maintaining aspect ratio. + /// + /// The stream to read the bitmap from. + /// The desired width of the resulting bitmap. + /// The to use should resizing be required. + /// An . + IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + /// + /// Loads a WriteableBitmap implementation from a stream to a specified height maintaining aspect ratio. + /// + /// The stream to read the bitmap from. + /// The desired height of the resulting bitmap. + /// The to use should resizing be required. + /// An . + IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + /// + /// Loads a WriteableBitmap implementation from a file.. + /// + /// The filename of the bitmap. + /// An . + IWriteableBitmapImpl LoadWriteableBitmap(string fileName); + + /// + /// Loads a WriteableBitmap implementation from a file.. + /// + /// The stream to read the bitmap from. + /// An . + IWriteableBitmapImpl LoadWriteableBitmap(Stream stream); + /// /// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio. /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 6d0be9f64d..2ed4948d41 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -77,6 +77,31 @@ namespace Avalonia.Skia return new ImmutableBitmap(stream); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableBitmapImpl(stream, width, true, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableBitmapImpl(stream, height, false, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + using (var stream = File.OpenRead(fileName)) + { + return LoadWriteableBitmap(stream); + } + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + return new WriteableBitmapImpl(stream); + } + /// public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index d48e7d10e6..c06bd0d057 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Threading; +using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -15,7 +17,70 @@ namespace Avalonia.Skia private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc; private readonly SKBitmap _bitmap; private readonly object _lock = new object(); + + /// + /// Create immutable bitmap from given stream. + /// + /// Stream containing encoded data. + public WriteableBitmapImpl(Stream stream) + { + using (var skiaStream = new SKManagedStream(stream)) + { + _bitmap = SKBitmap.Decode(skiaStream); + + if (_bitmap == null) + { + throw new ArgumentException("Unable to load bitmap from provided data"); + } + + PixelSize = new PixelSize(_bitmap.Width, _bitmap.Height); + Dpi = new Vector(96, 96); + } + } + + public WriteableBitmapImpl(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) + { + using (var skStream = new SKManagedStream(stream)) + using (var codec = SKCodec.Create(skStream)) + { + var info = codec.Info; + // get the scale that is nearest to what we want (eg: jpg returned 512) + var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height)); + + // decode the bitmap at the nearest size + var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height); + var bmp = SKBitmap.Decode(codec, nearest); + + // now scale that to the size that we want + var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height); + + SKImageInfo desired; + + + if (horizontal) + { + desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize)); + } + else + { + desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize); + } + + if (bmp.Width != desired.Width || bmp.Height != desired.Height) + { + var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality()); + bmp.Dispose(); + bmp = scaledBmp; + } + + _bitmap = bmp; + + PixelSize = new PixelSize(bmp.Width, bmp.Height); + Dpi = new Vector(96, 96); + } + } + /// /// Create new writeable bitmap. /// From a4ebe5dbb51d5a1370724c41a6913e1eeafcdb8e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:32:48 +0100 Subject: [PATCH 02/39] add direct2d implementation. --- .../HeadlessPlatformRenderInterface.cs | 22 +++++++++++++++++++ .../Avalonia.Direct2D1/Direct2D1Platform.cs | 22 +++++++++++++++++++ .../Media/Imaging/WriteableWicBitmapImpl.cs | 17 ++++++++++++++ .../MockPlatformRenderInterface.cs | 22 +++++++++++++++++++ 4 files changed, 83 insertions(+) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 62cac378d7..7b68a0250f 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -70,6 +70,28 @@ namespace Avalonia.Headless return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 6ae27870e8..e8757bbb34 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -188,6 +188,28 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(stream); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableWicBitmapImpl(stream, width, true, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableWicBitmapImpl(stream, height, false, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + return new WriteableWicBitmapImpl(fileName); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + return new WriteableWicBitmapImpl(stream); + } + /// public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 3261c45f15..fef6a9aa82 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -7,11 +8,27 @@ namespace Avalonia.Direct2D1.Media.Imaging { class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl { + public WriteableWicBitmapImpl(Stream stream, int decodeSize, bool horizontal, + Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + : base(stream, decodeSize, horizontal, interpolationMode) + { + } + public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat) : base(size, dpi, pixelFormat, alphaFormat) { } + public WriteableWicBitmapImpl(Stream stream) + : base(stream) + { + } + + public WriteableWicBitmapImpl(string fileName) + : base(fileName) + { + } + class LockedBitmap : ILockedFramebuffer { private readonly WriteableWicBitmapImpl _parent; diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index e73a76357a..b7f9cc34ca 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -66,6 +66,28 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(string fileName) { return Mock.Of(); From f9173594aeb884f19af3ffc80694950c0dede021 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:39:11 +0100 Subject: [PATCH 03/39] add api diff. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 2 +- src/Avalonia.Visuals/ApiCompatBaseline.txt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 3a6810eed9..613858056c 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -7,4 +7,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 7 +Total Issues: 8 diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 805d1955ea..a39a8600eb 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -6,4 +6,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. -Total Issues: 7 +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. +Total Issues: 11 From 79d96ca891fdc38be519255a5985f71354c65c67 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:45:35 +0100 Subject: [PATCH 04/39] implement missing stubs. --- .../NullRenderingPlatform.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 1570205456..eb5c031fd1 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -61,6 +61,28 @@ namespace Avalonia.Benchmarks throw new NotImplementedException(); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { throw new NotImplementedException(); From 57cbf67879dc8db19f440846679cce249efddf99 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:46:46 +0100 Subject: [PATCH 05/39] more missing stubs. --- .../VisualTree/MockRenderInterface.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 6d0683e699..6ce7f16d40 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -42,6 +42,28 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(string fileName) { throw new NotImplementedException(); From 265518859a228584d92e8de79571c54cdd47bef9 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 3 May 2021 07:34:10 +0200 Subject: [PATCH 06/39] Fix text tokenization for inherited scripts --- .../Media/TextFormatting/TextCharacters.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs index c6f524451b..86b96b9cfc 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs @@ -161,30 +161,27 @@ namespace Avalonia.Media.TextFormatting if (currentScript != script) { - if (currentScript != Script.Inherited && currentScript != Script.Common) + if (script == Script.Inherited || script == Script.Common) { - if (script == Script.Inherited || script == Script.Common) - { - script = currentScript; - } - else + script = currentScript; + } + else + { + if (currentScript != Script.Inherited && currentScript != Script.Common) { break; } } } - if (isFallback) + if (currentScript != Script.Common && currentScript != Script.Inherited) { - if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _)) + if (isFallback && defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _)) { break; } - } - if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _)) - { - if (!grapheme.FirstCodepoint.IsWhiteSpace) + if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _)) { break; } From 4340c69412211ce5675ec88a284dba36e407935f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 10 May 2021 21:57:13 +0100 Subject: [PATCH 07/39] fix comment --- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index c06bd0d057..85f9d8db91 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -19,7 +19,7 @@ namespace Avalonia.Skia private readonly object _lock = new object(); /// - /// Create immutable bitmap from given stream. + /// Create a WriteableBitmap from given stream. /// /// Stream containing encoded data. public WriteableBitmapImpl(Stream stream) From fec55f3f2792589b71e21e9a56f66fe899e6619b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 May 2021 22:24:41 +0100 Subject: [PATCH 08/39] fix nits. --- src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs | 4 ++-- src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs | 6 +++--- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index d10c743894..b723002ce0 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -59,7 +59,7 @@ namespace Avalonia.Media.Imaging /// The desired width of the resulting bitmap. /// The to use should any scaling be required. /// An instance of the class. - public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + public new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { var ri = AvaloniaLocator.Current.GetService(); @@ -74,7 +74,7 @@ namespace Avalonia.Media.Imaging /// The desired height of the resulting bitmap. /// The to use should any scaling be required. /// An instance of the class. - public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + public new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { var ri = AvaloniaLocator.Current.GetService(); diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 41f726ac96..de67aca5a8 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -106,7 +106,7 @@ namespace Avalonia.Platform /// The stream to read the bitmap from. /// The desired width of the resulting bitmap. /// The to use should resizing be required. - /// An . + /// An . IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); /// @@ -119,14 +119,14 @@ namespace Avalonia.Platform IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); /// - /// Loads a WriteableBitmap implementation from a file.. + /// Loads a WriteableBitmap implementation from a file. /// /// The filename of the bitmap. /// An . IWriteableBitmapImpl LoadWriteableBitmap(string fileName); /// - /// Loads a WriteableBitmap implementation from a file.. + /// Loads a WriteableBitmap implementation from a file. /// /// The stream to read the bitmap from. /// An . diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 85f9d8db91..63a1e8f869 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -34,7 +34,7 @@ namespace Avalonia.Skia } PixelSize = new PixelSize(_bitmap.Width, _bitmap.Height); - Dpi = new Vector(96, 96); + Dpi = SkiaPlatform.DefaultDpi; } } @@ -77,7 +77,7 @@ namespace Avalonia.Skia _bitmap = bmp; PixelSize = new PixelSize(bmp.Width, bmp.Height); - Dpi = new Vector(96, 96); + Dpi = SkiaPlatform.DefaultDpi; } } From b5d608e1c6b3b6813219f79dfab83c47ae8f6cee Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 12 May 2021 22:36:25 +0100 Subject: [PATCH 09/39] fixes from upstream, wraplayout. --- src/Avalonia.Layout/WrapLayout/WrapLayout.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Avalonia.Layout/WrapLayout/WrapLayout.cs b/src/Avalonia.Layout/WrapLayout/WrapLayout.cs index dccbb2205e..ded2afc3dd 100644 --- a/src/Avalonia.Layout/WrapLayout/WrapLayout.cs +++ b/src/Avalonia.Layout/WrapLayout/WrapLayout.cs @@ -236,6 +236,15 @@ namespace Avalonia.Layout // for the last condition it is zeros so adding it will make no difference // this way is faster than an if condition in every loop for checking the last item totalMeasure.U = parentMeasure.U; + + // Propagating an infinite size causes a crash. This can happen if the parent is scrollable and infinite in the opposite + // axis to the panel. Clearing to zero prevents the crash. + // This is likely an incorrect use of the control by the developer, however we need stability here so setting a default that wont crash. + if (double.IsInfinity(totalMeasure.U)) + { + totalMeasure.U = 0.0; + } + totalMeasure.V = state.GetHeight(); totalMeasure.U = Math.Ceiling(totalMeasure.U); From 6d0ac9a5417bdc01048b5e86f9e4a985f58ca091 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 13 May 2021 16:33:49 +0200 Subject: [PATCH 10/39] Fix property type on Stretch. --- samples/ControlCatalog/Pages/ViewboxPage.xaml | 7 ++++--- samples/ControlCatalog/Pages/ViewboxPage.xaml.cs | 16 +++------------- src/Avalonia.Controls/Viewbox.cs | 3 ++- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml index 6e649b39a5..ef802db33e 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -12,8 +12,9 @@ - + @@ -25,7 +26,7 @@ - + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs index 7a2bceb8f2..94b3f3ea14 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs @@ -6,23 +6,18 @@ namespace ControlCatalog.Pages { public class ViewboxPage : UserControl { - private readonly Viewbox _viewbox; - private readonly ComboBox _stretchSelector; - public ViewboxPage() { InitializeComponent(); - _viewbox = this.FindControl("Viewbox"); - - _stretchSelector = this.FindControl("StretchSelector"); + var stretchSelector = this.FindControl("StretchSelector"); - _stretchSelector.Items = new[] + stretchSelector.Items = new[] { Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None }; - _stretchSelector.SelectedIndex = 0; + stretchSelector.SelectedIndex = 0; var stretchDirectionSelector = this.FindControl("StretchDirectionSelector"); @@ -38,10 +33,5 @@ namespace ControlCatalog.Pages { AvaloniaXamlLoader.Load(this); } - - private void StretchSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - _viewbox.Stretch = (Stretch) _stretchSelector.SelectedItem!; - } } } diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index b65f4b31d8..15ca070de2 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -12,7 +12,8 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly AvaloniaProperty StretchProperty = - AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); + AvaloniaProperty.RegisterDirect(nameof(Stretch), + v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); /// /// Defines the property. From 59d51d6a56d1a6b62051e656be331b7cea39f668 Mon Sep 17 00:00:00 2001 From: Splitwirez <34009058+Splitwirez@users.noreply.github.com> Date: Fri, 14 May 2021 00:10:50 -0700 Subject: [PATCH 11/39] New Fluent expander style (#5573) * Updated Fluent theme Expander style. closes #5358 * Fixed padding mismatch between header and content * Fixed CornerRadii, unleashed the c o l o u r e s * Use CornerRadiusFilterConverter instead of expander specific converters * Formatting * Fix default theme in ControlCatalog Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com> Co-authored-by: Max Katz --- samples/ControlCatalog/App.xaml.cs | 8 + src/Avalonia.Themes.Fluent/Accents/Base.xaml | 8 +- .../Controls/Expander.xaml | 317 +++++++++++------- 3 files changed, 219 insertions(+), 114 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 020fb2fff3..f3ec7b48aa 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -39,6 +39,10 @@ namespace ControlCatalog public static Styles DefaultLight = new Styles { + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml") + }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") @@ -60,6 +64,10 @@ namespace ControlCatalog public static Styles DefaultDark = new Styles { + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml") + }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml index 8597c76998..1e2acf736d 100644 --- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml @@ -1,6 +1,7 @@ diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index a6371a5be9..3f70939953 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -1,141 +1,232 @@ - + + + + + + + Expanded content + + + + + Expanded content + + + + + Expanded content + + + + + Expanded content + + + + + + + 16 + 16 + + 1 + + 1,1,0,1 + 1,1,1,0 + 0,1,1,1 + 1,0,1,1 + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + From d64a66d441ad9846065c426ddef61800af22950b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 14 May 2021 11:17:45 +0200 Subject: [PATCH 12/39] Fix fallback selection --- .../TextFormatting/ShapedTextCharacters.cs | 6 +++-- .../Media/TextFormatting/TextCharacters.cs | 27 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index 723d5e81ab..b304b19910 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs @@ -109,7 +109,8 @@ namespace Avalonia.Media.TextFormatting GlyphRun.GlyphAdvances.Take(glyphCount), GlyphRun.GlyphOffsets.Take(glyphCount), GlyphRun.Characters.Take(length), - GlyphRun.GlyphClusters.Take(glyphCount)); + GlyphRun.GlyphClusters.Take(glyphCount), + GlyphRun.BiDiLevel); var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties); @@ -120,7 +121,8 @@ namespace Avalonia.Media.TextFormatting GlyphRun.GlyphAdvances.Skip(glyphCount), GlyphRun.GlyphOffsets.Skip(glyphCount), GlyphRun.Characters.Skip(length), - GlyphRun.GlyphClusters.Skip(glyphCount)); + GlyphRun.GlyphClusters.Skip(glyphCount), + GlyphRun.BiDiLevel); var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties); diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs index 86b96b9cfc..0779716ec8 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs @@ -135,20 +135,20 @@ namespace Avalonia.Media.TextFormatting count = 0; var script = Script.Common; - //var direction = BiDiClass.LeftToRight; + var direction = BiDiClass.LeftToRight; var font = typeface.GlyphTypeface; var defaultFont = defaultTypeface.GlyphTypeface; - + var enumerator = new GraphemeEnumerator(text); while (enumerator.MoveNext()) { - var grapheme = enumerator.Current; + var currentGrapheme = enumerator.Current; - var currentScript = grapheme.FirstCodepoint.Script; + var currentScript = currentGrapheme.FirstCodepoint.Script; - //var currentDirection = grapheme.FirstCodepoint.BiDiClass; + var currentDirection = currentGrapheme.FirstCodepoint.BiDiClass; //// ToDo: Implement BiDi algorithm //if (currentScript.HorizontalDirection != direction) @@ -176,18 +176,29 @@ namespace Avalonia.Media.TextFormatting if (currentScript != Script.Common && currentScript != Script.Inherited) { - if (isFallback && defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _)) + if (isFallback && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } - if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _)) + if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } } - count += grapheme.Text.Length; + if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + { + break; + } + + if (direction == BiDiClass.RightToLeft && currentDirection == BiDiClass.CommonSeparator) + { + break; + } + + count += currentGrapheme.Text.Length; + direction = currentDirection; } return count > 0; From 690a5dde929b678e1896d4817df6d1f7e680df72 Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Fri, 14 May 2021 12:42:50 +0200 Subject: [PATCH 13/39] HitTestEnumerator: Account for ICustomSimpleHitTest --- src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 6a4c532d4a..608ad7ab75 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -256,7 +256,7 @@ namespace Avalonia.Rendering.SceneGraph if (childCount == 0 || wasVisited) { - if ((wasVisited || FilterAndClip(node, ref clip)) && node.HitTest(_point)) + if ((wasVisited || FilterAndClip(node, ref clip)) && (node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) { _current = node.Visual; From ad14ece46860b6f59a7fe883e4dea42186135676 Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Fri, 14 May 2021 16:49:49 +0200 Subject: [PATCH 14/39] HitTestEnumerator: FilterAndClip don't transform point for ICustomHitTest --- src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 2 +- src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 9ea1b84311..85feb06c44 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -225,7 +225,7 @@ namespace Avalonia.Rendering if (filter?.Invoke(visual) != false) { - bool containsPoint = false; + bool containsPoint; if (visual is ICustomSimpleHitTest custom) { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 608ad7ab75..36aa08c2f9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -256,7 +256,8 @@ namespace Avalonia.Rendering.SceneGraph if (childCount == 0 || wasVisited) { - if ((wasVisited || FilterAndClip(node, ref clip)) && (node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) + if ((wasVisited || FilterAndClip(node, ref clip)) && + (node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) { _current = node.Visual; @@ -311,8 +312,7 @@ namespace Avalonia.Rendering.SceneGraph if (!clipped && node.Visual is ICustomHitTest custom) { - var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual); - clipped = !custom.HitTest(controlPoint.Value); + clipped = !custom.HitTest(_point); } return !clipped; From 5f4a338c18c0cdad8f7aa83cb23562e19614f9d1 Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Fri, 14 May 2021 16:56:53 +0200 Subject: [PATCH 15/39] ICustomHitTest renderer tests --- .../Rendering/CustomHitTestBorder.cs | 17 ++++++++ .../DeferredRendererTests_HitTesting.cs | 43 +++++++++++++++++-- .../ImmediateRendererTests_HitTesting.cs | 37 ++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs new file mode 100644 index 0000000000..3221f097ad --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering; + +namespace Avalonia.Visuals.UnitTests.Rendering +{ + internal class CustomHitTestBorder : Border, ICustomHitTest + { + public bool HitTest(Point point) + { + // Move hit testing window halfway to the left + return Bounds + .WithX(Bounds.X - Bounds.Width / 2) + .Contains(point); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 90b01e2ddb..d24d183709 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -1,6 +1,9 @@ -using System.Linq; +using System; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Presenters; +using Avalonia.Controls.Shapes; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; @@ -9,8 +12,6 @@ using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; using Xunit; -using System; -using Avalonia.Controls.Shapes; namespace Avalonia.Visuals.UnitTests.Rendering { @@ -501,6 +502,42 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } + [Fact] + public void HitTest_Should_Accommodate_ICustomHitTest() + { + using (TestApplication()) + { + Border border; + + var root = new TestRoot + { + Width = 300, + Height = 200, + Child = border = new CustomHitTestBorder + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(75, 100), root, null); + Assert.Equal(new[] { border }, result); + + result = root.Renderer.HitTest(new Point(125, 100), root, null); + Assert.Equal(new[] { border }, result); + + result = root.Renderer.HitTest(new Point(175, 100), root, null); + Assert.Empty(result); + } + } + private IDisposable TestApplication() { return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs index c4ef9d5ee7..a3b0a0cdd5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs @@ -413,6 +413,43 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } + [Fact] + public void HitTest_Should_Accommodate_ICustomHitTest() + { + using (TestApplication()) + { + Border border; + + var root = new TestRoot + { + Width = 300, + Height = 200, + Child = border = new CustomHitTestBorder + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; + + root.Renderer = new ImmediateRenderer(root); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + root.Renderer.Paint(new Rect(root.ClientSize)); + + var result = root.Renderer.HitTest(new Point(75, 100), root, null).First(); + Assert.Equal(border, result); + + result = root.Renderer.HitTest(new Point(125, 100), root, null).First(); + Assert.Equal(border, result); + + result = root.Renderer.HitTest(new Point(175, 100), root, null).First(); + Assert.Equal(root, result); + } + } + private IDisposable TestApplication() { return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); From 98e773ab8ad11b077e7d04be93e0dd219d123262 Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Fri, 14 May 2021 16:57:50 +0200 Subject: [PATCH 16/39] ICustomHitTest: Remove comment, expanding the hittable area is possible --- src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs index 245888a351..f71f9879e6 100644 --- a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs +++ b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs @@ -17,10 +17,6 @@ namespace Avalonia.Rendering /// /// Allows customization of hit-testing for all renderers. /// - /// - /// Note that this interface can only used to make a portion of a control non-hittable, it - /// cannot expand the hittable area of a control. - /// public interface ICustomHitTest : ICustomSimpleHitTest { } From 8c2573553318fa9d48eb641f45c00f635b728600 Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Fri, 14 May 2021 17:10:20 +0200 Subject: [PATCH 17/39] ICustomSimpleHitTest: Clarify with XML comment that point is in global space --- src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs index f71f9879e6..354a344ffe 100644 --- a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs +++ b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs @@ -11,6 +11,7 @@ namespace Avalonia.Rendering /// public interface ICustomSimpleHitTest { + /// The point to hit test in global coordinate space. bool HitTest(Point point); } From 4db1f41f72ccec0f076015513795ca7e9089987d Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 14 May 2021 22:58:51 +0200 Subject: [PATCH 18/39] Change Viewbox.Stretch to a styled property --- src/Avalonia.Controls/ApiCompatBaseline.txt | 3 ++- src/Avalonia.Controls/Viewbox.cs | 11 ++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 7199c15d21..a79b3b4d7b 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -4,10 +4,11 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. +MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 11 +Total Issues: 12 diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 15ca070de2..624c61bb82 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -11,9 +11,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly AvaloniaProperty StretchProperty = - AvaloniaProperty.RegisterDirect(nameof(Stretch), - v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); + public static readonly StyledProperty StretchProperty = + AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); /// /// Defines the property. @@ -21,8 +20,6 @@ namespace Avalonia.Controls public static readonly StyledProperty StretchDirectionProperty = AvaloniaProperty.Register(nameof(StretchDirection), StretchDirection.Both); - private Stretch _stretch = Stretch.Uniform; - static Viewbox() { ClipToBoundsProperty.OverrideDefaultValue(true); @@ -35,8 +32,8 @@ namespace Avalonia.Controls /// public Stretch Stretch { - get => _stretch; - set => SetAndRaise(StretchProperty, ref _stretch, value); + get => GetValue(StretchProperty); + set => SetValue(StretchProperty, value); } /// From 752412933047cb74f0e2ad5a9dd2dcc6c295d036 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 15 May 2021 00:39:11 +0200 Subject: [PATCH 19/39] Cleanup layout visualizer and improve UX. --- .../ViewModels/ControlLayoutViewModel.cs | 29 ++- .../Diagnostics/Views/ControlDetailsView.xaml | 185 +++++++++++------- .../Diagnostics/Views/ThicknessEditor.cs | 21 +- 3 files changed, 144 insertions(+), 91 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index fd2e4c3355..ef227f7374 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -1,4 +1,6 @@ +using System; using System.ComponentModel; +using System.Text; using Avalonia.Controls; using Avalonia.Layout; using Avalonia.VisualTree; @@ -95,12 +97,27 @@ namespace Avalonia.Diagnostics.ViewModels { string CreateConstraintInfo(StyledProperty minProperty, StyledProperty maxProperty) { - if (ao.IsSet(minProperty) || ao.IsSet(maxProperty)) + bool hasMin = ao.IsSet(minProperty); + bool hasMax = ao.IsSet(maxProperty); + + if (hasMin || hasMax) { - var minValue = ao.GetValue(minProperty); - var maxValue = ao.GetValue(maxProperty); + var builder = new StringBuilder(); + + if (hasMin) + { + var minValue = ao.GetValue(minProperty); + builder.AppendFormat("Min: {0}", Math.Round(minValue, 2)); + builder.AppendLine(); + } + + if (hasMax) + { + var maxValue = ao.GetValue(maxProperty); + builder.AppendFormat("Max: {0}", Math.Round(maxValue, 2)); + } - return $"{minValue} < size < {maxValue}"; + return builder.ToString(); } return null; @@ -183,8 +200,8 @@ namespace Avalonia.Diagnostics.ViewModels { var size = _control.Bounds; - Width = size.Width; - Height = size.Height; + Width = Math.Round(size.Width, 2); + Height = Math.Round(size.Height, 2); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 9ba576c826..22193985b4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -8,11 +8,16 @@ + + + + + @@ -24,43 +29,60 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -110,47 +132,66 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs index c7611c8c46..2f03a23fdf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs @@ -1,21 +1,9 @@ using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Data.Converters; using Avalonia.Media; namespace Avalonia.Diagnostics.Views { - internal static class Converters - { - public static IValueConverter HasConstraintConverter = - new FuncValueConverter(ConvertToDecoration); - - private static TextDecorationCollection ConvertToDecoration(object arg) - { - return arg != null ? TextDecorations.Underline : null; - } - } - internal class ThicknessEditor : ContentControl { public static readonly DirectProperty ThicknessProperty = @@ -44,6 +32,14 @@ namespace Avalonia.Diagnostics.Views AvaloniaProperty.RegisterDirect(nameof(Bottom), o => o.Bottom, (o, v) => o.Bottom = v); + public static readonly StyledProperty HighlightProperty = + AvaloniaProperty.Register(nameof(Highlight)); + + public IBrush Highlight + { + get => GetValue(HighlightProperty); + set => SetValue(HighlightProperty, value); + } private Thickness _thickness; private string _header; @@ -52,7 +48,6 @@ namespace Avalonia.Diagnostics.Views private double _top; private double _right; private double _bottom; - private bool _isUpdatingThickness; public Thickness Thickness From b059a8a178a9617abac851803ea7d5cc89cb6460 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 15 May 2021 13:26:11 +0200 Subject: [PATCH 20/39] Fix highlight disappearing over guidelines. --- .../Diagnostics/Views/ControlDetailsView.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 22193985b4..50534e77b8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -183,6 +183,7 @@ + From 4d4ca6806ce9b90ae84cb4c3f311be275ed39667 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 15 May 2021 15:53:42 +0200 Subject: [PATCH 21/39] Avoid allocating lists in Classes. --- src/Avalonia.Styling/Controls/Classes.cs | 39 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Styling/Controls/Classes.cs b/src/Avalonia.Styling/Controls/Classes.cs index 4e2783d4ec..50605661fa 100644 --- a/src/Avalonia.Styling/Controls/Classes.cs +++ b/src/Avalonia.Styling/Controls/Classes.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using Avalonia.Collections; +#nullable enable + namespace Avalonia.Controls { /// @@ -90,7 +92,7 @@ namespace Avalonia.Controls } /// - /// Remvoes all non-pseudoclasses from the collection. + /// Removes all non-pseudoclasses from the collection. /// public override void Clear() { @@ -135,7 +137,7 @@ namespace Avalonia.Controls /// public override void InsertRange(int index, IEnumerable names) { - var c = new List(); + List? toInsert = null; foreach (var name in names) { @@ -143,11 +145,16 @@ namespace Avalonia.Controls if (!Contains(name)) { - c.Add(name); + toInsert ??= new List(); + + toInsert.Add(name); } } - base.InsertRange(index, c); + if (toInsert != null) + { + base.InsertRange(index, toInsert); + } } /// @@ -176,19 +183,21 @@ namespace Avalonia.Controls /// public override void RemoveAll(IEnumerable names) { - var c = new List(); + List? toRemove = null; foreach (var name in names) { ThrowIfPseudoclass(name, "removed"); - if (Contains(name)) - { - c.Add(name); - } + toRemove ??= new List(); + + toRemove.Add(name); } - base.RemoveAll(c); + if (toRemove != null) + { + base.RemoveAll(toRemove); + } } /// @@ -223,7 +232,7 @@ namespace Avalonia.Controls /// The new contents of the collection. public void Replace(IList source) { - var toRemove = new List(); + List? toRemove = null; foreach (var name in source) { @@ -234,11 +243,17 @@ namespace Avalonia.Controls { if (!name.StartsWith(":")) { + toRemove ??= new List(); + toRemove.Add(name); } } - base.RemoveAll(toRemove); + if (toRemove != null) + { + base.RemoveAll(toRemove); + } + base.AddRange(source); } From 08382e5bf554381a327e215b191d901ab4b16663 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 20:52:59 -0400 Subject: [PATCH 22/39] Fix SolidColorBrushAnimator NRE --- .../Animation/Animators/SolidColorBrushAnimator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index cec96fecf8..a56cc1de8c 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -12,6 +12,11 @@ namespace Avalonia.Animation.Animators { public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue) { + if (oldValue is null || newValue is null) + { + return oldValue; + } + return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); } @@ -26,6 +31,11 @@ namespace Avalonia.Animation.Animators { public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue) { + if (oldValue is null || newValue is null) + { + return oldValue; + } + return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); } } From 34505c4c8b41c9f98553e4b2f500a8462be5811f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 20:53:28 -0400 Subject: [PATCH 23/39] Reuse Animator.Interpolate in transitions code --- .../Transitions/DoubleTransition.cs | 10 ++++----- .../Transitions/FloatTransition.cs | 7 +++++-- .../Transitions/IntegerTransition.cs | 7 +++++-- .../Transitions/CornerRadiusTransition.cs | 21 +++++-------------- .../Animation/Transitions/PointTransition.cs | 10 ++++----- .../Animation/Transitions/SizeTransition.cs | 10 ++++----- .../Transitions/ThicknessTransition.cs | 10 ++++----- .../Animation/Transitions/VectorTransition.cs | 10 ++++----- 8 files changed, 40 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index 8cae1e1f81..d5bb1aac20 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class DoubleTransition : Transition { + private static readonly DoubleAnimator s_animator = new DoubleAnimator(); + /// public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Animation/Transitions/FloatTransition.cs b/src/Avalonia.Animation/Transitions/FloatTransition.cs index 427563e559..37b644fa96 100644 --- a/src/Avalonia.Animation/Transitions/FloatTransition.cs +++ b/src/Avalonia.Animation/Transitions/FloatTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,12 +10,13 @@ namespace Avalonia.Animation /// public class FloatTransition : Transition { + private static readonly FloatAnimator s_animator = new FloatAnimator(); + /// public override IObservable DoTransition(IObservable progress, float oldValue, float newValue) { - var delta = newValue - oldValue; return progress - .Select(p => (float)Easing.Ease(p) * delta + oldValue); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Animation/Transitions/IntegerTransition.cs b/src/Avalonia.Animation/Transitions/IntegerTransition.cs index 7a85bd75dc..223b2ba531 100644 --- a/src/Avalonia.Animation/Transitions/IntegerTransition.cs +++ b/src/Avalonia.Animation/Transitions/IntegerTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,12 +10,13 @@ namespace Avalonia.Animation /// public class IntegerTransition : Transition { + private static readonly Int32Animator s_animator = new Int32Animator(); + /// public override IObservable DoTransition(IObservable progress, int oldValue, int newValue) { - var delta = newValue - oldValue; return progress - .Select(p => (int)(Easing.Ease(p) * delta + oldValue)); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs index 0b0f04ca94..9ffdf53694 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,26 +10,13 @@ namespace Avalonia.Animation /// public class CornerRadiusTransition : Transition { + private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator(); + /// public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - - var deltaTL = newValue.TopLeft - oldValue.TopLeft; - var deltaTR = newValue.TopRight - oldValue.TopRight; - var deltaBR = newValue.BottomRight - oldValue.BottomRight; - var deltaBL = newValue.BottomLeft - oldValue.BottomLeft; - - var nTL = f * deltaTL + oldValue.TopLeft; - var nTR = f * deltaTR + oldValue.TopRight; - var nBR = f * deltaBR + oldValue.BottomRight; - var nBL = f * deltaBL + oldValue.BottomLeft; - - return new CornerRadius(nTL, nTR, nBR, nBL); - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs index 29db5fc868..fbe24c6d55 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class PointTransition : Transition { + private static readonly PointAnimator s_animator = new PointAnimator(); + /// public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs index b40e789915..464f83bec7 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class SizeTransition : Transition { + private static readonly SizeAnimator s_animator = new SizeAnimator(); + /// public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs index 28d4ea067f..9fb3380780 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class ThicknessTransition : Transition { + private static readonly ThicknessAnimator s_animator = new ThicknessAnimator(); + /// public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs index c073e8e192..5038117faa 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class VectorTransition : Transition { + private static readonly VectorAnimator s_animator = new VectorAnimator(); + /// public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } From f29d050e73c0144909757c085bf6bcd5a156f7ae Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 20:53:45 -0400 Subject: [PATCH 24/39] Add BoxShadows transition --- .../Transitions/BoxShadowsTransition.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs diff --git a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs new file mode 100644 index 0000000000..008613fb40 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs @@ -0,0 +1,23 @@ +using System; +using System.Reactive.Linq; + +using Avalonia.Animation.Animators; +using Avalonia.Media; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class BoxShadowsTransition : Transition + { + private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator(); + + /// + public override IObservable DoTransition(IObservable progress, BoxShadows oldValue, BoxShadows newValue) + { + return progress + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); + } + } +} From f5a01afc6de7ac794d9dfb1a16792cf7f6296ee0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 22:05:01 -0400 Subject: [PATCH 25/39] Update RenderDemo pages --- samples/RenderDemo/Pages/AnimationsPage.xaml | 7 +- samples/RenderDemo/Pages/TransitionsPage.xaml | 65 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 12fb31ea59..21c7d68b5d 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -1,7 +1,8 @@ + x:Class="RenderDemo.Pages.AnimationsPage" + MaxWidth="600"> @@ -167,8 +168,8 @@ - - Hover to activate Transform Keyframe Animations. + + Hover to activate Keyframe Animations.