From 3837ce8de48fb965dcf80986c2eb0d3a68ff9946 Mon Sep 17 00:00:00 2001 From: Roman Kalachik Date: Thu, 29 Feb 2024 16:07:47 +0300 Subject: [PATCH 1/7] fixed issue 206, added tests --- .../SvgCustomDrawOperation.cs | 39 +- src/Svg.Skia/SKSvg.Model.cs | 27 +- tests/Avalonia.Svg.Skia.UnitTests/App.axaml | 7 + .../Avalonia.Svg.Skia.UnitTests/App.axaml.cs | 25 + .../Assets/__tiger.svg | 1978 +++++++++++++++++ .../Avalonia.Svg.Skia.UnitTests.csproj | 4 +- .../AvaloniaApp.cs | 33 + .../AvaloniaUiTestFramework.cs | 112 + .../SvgImageReloadTests.cs | 57 + 9 files changed, 2252 insertions(+), 30 deletions(-) create mode 100644 tests/Avalonia.Svg.Skia.UnitTests/App.axaml create mode 100644 tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs create mode 100644 tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg create mode 100644 tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs create mode 100644 tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs create mode 100644 tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs diff --git a/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs b/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs index 3d4660156..2223b0574 100644 --- a/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs +++ b/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs @@ -28,25 +28,28 @@ public void Dispose() public void Render(ImmediateDrawingContext context) { - if (_svg?.Picture is null) + lock (_svg.Locker) { - return; + if (_svg?.Picture is null) + { + return; + } + + var leaseFeature = context.TryGetFeature(); + if (leaseFeature is null) + { + return; + } + using var lease = leaseFeature.Lease(); + var canvas = lease?.SkCanvas; + if (canvas is null) + { + return; + } + + canvas.Save(); + canvas.DrawPicture(_svg.Picture); + canvas.Restore(); } - - var leaseFeature = context.TryGetFeature(); - if (leaseFeature is null) - { - return; - } - using var lease = leaseFeature.Lease(); - var canvas = lease?.SkCanvas; - if (canvas is null) - { - return; - } - - canvas.Save(); - canvas.DrawPicture(_svg.Picture); - canvas.Restore(); } } diff --git a/src/Svg.Skia/SKSvg.Model.cs b/src/Svg.Skia/SKSvg.Model.cs index f2415fd88..31088ef29 100644 --- a/src/Svg.Skia/SKSvg.Model.cs +++ b/src/Svg.Skia/SKSvg.Model.cs @@ -95,6 +95,8 @@ public static void Draw(SkiaSharp.SKCanvas skCanvas, string path, SkiaModel skia public virtual SkiaSharp.SKPicture? Picture { get; protected set; } + public object Locker { get; } = new object(); + public SvgParameters? Parameters => _originalParameters; public SKSvg() @@ -181,23 +183,26 @@ public SKSvg() public SkiaSharp.SKPicture? ReLoad(SvgParameters? parameters) { - if (!CacheOriginalStream) + lock (Locker) { - throw new ArgumentException($"Enable {nameof(CacheOriginalStream)} feature toggle to enable reload feature."); - } + if (!CacheOriginalStream) + { + throw new ArgumentException($"Enable {nameof(CacheOriginalStream)} feature toggle to enable reload feature."); + } - Reset(); + Reset(); - _originalParameters = parameters; + _originalParameters = parameters; - if (_originalStream == null) - { - return Load(_originalPath, parameters); - } + if (_originalStream == null) + { + return Load(_originalPath, parameters); + } - _originalStream.Position = 0; + _originalStream.Position = 0; - return Load(_originalStream, parameters); + return Load(_originalStream, parameters); + } } public SkiaSharp.SKPicture? FromSvg(string svg) diff --git a/tests/Avalonia.Svg.Skia.UnitTests/App.axaml b/tests/Avalonia.Svg.Skia.UnitTests/App.axaml new file mode 100644 index 000000000..96409ddcd --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UnitTests/App.axaml @@ -0,0 +1,7 @@ + + + + + diff --git a/tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs b/tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs new file mode 100644 index 000000000..9ce4152be --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.Primitives; +using Avalonia.Markup.Xaml; + +namespace UITests +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new Window(); + } + base.OnFrameworkInitializationCompleted(); + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg b/tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg new file mode 100644 index 000000000..11abcd061 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg @@ -0,0 +1,1978 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj b/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj index 688b48774..8c97e40f2 100644 --- a/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj +++ b/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj @@ -14,8 +14,10 @@ + - + + diff --git a/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs b/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs new file mode 100644 index 000000000..c6cd1da40 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs @@ -0,0 +1,33 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; + +namespace UITests +{ + public static class AvaloniaApp + { + public static void Stop() + { + var app = GetApp(); + if (app is IDisposable disposable) + { + Dispatcher.UIThread.Post(disposable.Dispose); + } + + if (app != null) + Dispatcher.UIThread.Post(() => app.Shutdown()); + } + + public static Window? GetMainWindow() => GetApp()?.MainWindow; + + public static IClassicDesktopStyleApplicationLifetime? GetApp() => + (IClassicDesktopStyleApplicationLifetime?)Application.Current?.ApplicationLifetime; + + public static AppBuilder BuildAvaloniaApp() => + AppBuilder + .Configure() + .UsePlatformDetect(); + } +} diff --git a/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs b/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs new file mode 100644 index 000000000..54ca14efc --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Avalonia; + +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +[assembly: TestFramework("UITests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UnitTests")] +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] +namespace UITests +{ + + public class AvaloniaUiTestFramework : XunitTestFramework + { + public AvaloniaUiTestFramework(IMessageSink messageSink) + : base(messageSink) + { + } + + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); + + private class Executor : XunitTestFrameworkExecutor + { + public Executor( + AssemblyName assemblyName, + ISourceInformationProvider sourceInformationProvider, + IMessageSink diagnosticMessageSink) + : base( + assemblyName, + sourceInformationProvider, + diagnosticMessageSink) + { + + } + + protected override async void RunTestCases(IEnumerable testCases, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + { + executionOptions.SetValue("xunit.execution.DisableParallelization", false); + using var assemblyRunner = new Runner( + TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, + executionOptions); + + await assemblyRunner.RunAsync(); + } + } + + private class Runner : XunitTestAssemblyRunner + { + public Runner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + : base( + testAssembly, + testCases, + diagnosticMessageSink, + executionMessageSink, + executionOptions) + { + + } + + public override void Dispose() + { + AvaloniaApp.Stop(); + + base.Dispose(); + } + + protected override void SetupSyncContext(int maxParallelThreads) + { + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + //AvaloniaApp.RegisterDependencies(); + + AvaloniaApp + .BuildAvaloniaApp() + .AfterSetup(_ => + { + tcs.SetResult(SynchronizationContext.Current!); + }) + .StartWithClassicDesktopLifetime(new string[0]); + } + catch (Exception e) + { + tcs.SetException(e); + } + }) + { + IsBackground = true + }; + + thread.Start(); + + SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); + } + } + } +} diff --git a/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs b/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs new file mode 100644 index 000000000..9c7b9af8a --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Threading; +using Svg.Skia; +using Xunit; + +namespace Avalonia.Svg.Skia.UnitTests; + +public class SvgImageReloadTests +{ + Image test; + private string css = ".Black { fill: #FF0000; }"; + Window window; + [Fact] + public async void SvgImage_ReLoad() + { + SKSvg.CacheOriginalStream = true; + var uri = new Uri($"avares://Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg"); + var assetLoader = new StandardAssetLoader(); // AvaloniaLocator.Current.GetService() + + var svgFile = assetLoader.Open(uri); + + var svgSource = SvgSource.LoadFromStream(svgFile); + var svgImage = new SvgImage() { Source = svgSource }; + + test = new Image(); + test.Source = svgImage; + + window = new Window(); + window.Content = test; + window.Show(); + + var timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromMilliseconds(10); + timer.Tick += Timer_Tick; + timer.Start(); + + await Task.Delay(10000); + + timer?.Stop(); + window?.Close(); + } + + + + private void Timer_Tick(object sender, EventArgs e) + { + var image = (SvgImage)test.Source; + (image.CurrentCss, css) = (css, image.CurrentCss); + test.InvalidateVisual(); + } +} From 101c86ed454eb02b5eebb9c8e072be46f64c1532 Mon Sep 17 00:00:00 2001 From: Roman Kalachik Date: Thu, 29 Feb 2024 16:21:56 +0300 Subject: [PATCH 2/7] restore CacheOriginalStream flag --- tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs b/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs index 9c7b9af8a..dd73f3791 100644 --- a/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs +++ b/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs @@ -44,6 +44,7 @@ public async void SvgImage_ReLoad() timer?.Stop(); window?.Close(); + SKSvg.CacheOriginalStream = false; } From e4f29f447d37ffcf2f8d7e98446784337e56149d Mon Sep 17 00:00:00 2001 From: Roman Kalachik Date: Thu, 29 Feb 2024 16:51:49 +0300 Subject: [PATCH 3/7] refactoring --- .../SvgCustomDrawOperation.cs | 35 ++++++++++--------- src/Svg.Skia/SKSvg.Model.cs | 7 ++-- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs b/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs index 2223b0574..2774d78f4 100644 --- a/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs +++ b/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs @@ -28,27 +28,28 @@ public void Dispose() public void Render(ImmediateDrawingContext context) { + if (_svg == null) + return; + + + var leaseFeature = context.TryGetFeature(); + if (leaseFeature is null) + { + return; + } + using var lease = leaseFeature.Lease(); + var canvas = lease?.SkCanvas; + if (canvas is null) + { + return; + } lock (_svg.Locker) { - if (_svg?.Picture is null) - { - return; - } - - var leaseFeature = context.TryGetFeature(); - if (leaseFeature is null) - { + var picture = _svg.Picture; + if (picture is null) return; - } - using var lease = leaseFeature.Lease(); - var canvas = lease?.SkCanvas; - if (canvas is null) - { - return; - } - canvas.Save(); - canvas.DrawPicture(_svg.Picture); + canvas.DrawPicture(picture); canvas.Restore(); } } diff --git a/src/Svg.Skia/SKSvg.Model.cs b/src/Svg.Skia/SKSvg.Model.cs index 31088ef29..0cdce7c52 100644 --- a/src/Svg.Skia/SKSvg.Model.cs +++ b/src/Svg.Skia/SKSvg.Model.cs @@ -253,8 +253,11 @@ private void Reset() { Model = null; Drawable = null; - Picture?.Dispose(); - Picture = null; + lock (Locker) + { + Picture?.Dispose(); + Picture = null; + } } public void Dispose() From 30693ceee2039d7f199f3b1b1e736b1da4bff6cb Mon Sep 17 00:00:00 2001 From: Roman Kalachik Date: Thu, 29 Feb 2024 16:57:43 +0300 Subject: [PATCH 4/7] moved UiTests --- tests/Avalonia.Svg.Skia.UnitTests/App.axaml | 7 - .../Avalonia.Svg.Skia.UnitTests/App.axaml.cs | 25 - .../Assets/__tiger.svg | 1978 ----------------- .../Avalonia.Svg.Skia.UnitTests.csproj | 2 - .../AvaloniaApp.cs | 33 - .../AvaloniaUiTestFramework.cs | 112 - .../SvgImageReloadTests.cs | 58 - 7 files changed, 2215 deletions(-) delete mode 100644 tests/Avalonia.Svg.Skia.UnitTests/App.axaml delete mode 100644 tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs delete mode 100644 tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg delete mode 100644 tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs delete mode 100644 tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs delete mode 100644 tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs diff --git a/tests/Avalonia.Svg.Skia.UnitTests/App.axaml b/tests/Avalonia.Svg.Skia.UnitTests/App.axaml deleted file mode 100644 index 96409ddcd..000000000 --- a/tests/Avalonia.Svg.Skia.UnitTests/App.axaml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs b/tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs deleted file mode 100644 index 9ce4152be..000000000 --- a/tests/Avalonia.Svg.Skia.UnitTests/App.axaml.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls.Primitives; -using Avalonia.Markup.Xaml; - -namespace UITests -{ - public partial class App : Application - { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.MainWindow = new Window(); - } - base.OnFrameworkInitializationCompleted(); - } - } -} \ No newline at end of file diff --git a/tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg b/tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg deleted file mode 100644 index 11abcd061..000000000 --- a/tests/Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg +++ /dev/null @@ -1,1978 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj b/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj index 8c97e40f2..f5eaccb3e 100644 --- a/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj +++ b/tests/Avalonia.Svg.Skia.UnitTests/Avalonia.Svg.Skia.UnitTests.csproj @@ -14,9 +14,7 @@ - - diff --git a/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs b/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs deleted file mode 100644 index c6cd1da40..000000000 --- a/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaApp.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Threading; - -namespace UITests -{ - public static class AvaloniaApp - { - public static void Stop() - { - var app = GetApp(); - if (app is IDisposable disposable) - { - Dispatcher.UIThread.Post(disposable.Dispose); - } - - if (app != null) - Dispatcher.UIThread.Post(() => app.Shutdown()); - } - - public static Window? GetMainWindow() => GetApp()?.MainWindow; - - public static IClassicDesktopStyleApplicationLifetime? GetApp() => - (IClassicDesktopStyleApplicationLifetime?)Application.Current?.ApplicationLifetime; - - public static AppBuilder BuildAvaloniaApp() => - AppBuilder - .Configure() - .UsePlatformDetect(); - } -} diff --git a/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs b/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs deleted file mode 100644 index 54ca14efc..000000000 --- a/tests/Avalonia.Svg.Skia.UnitTests/AvaloniaUiTestFramework.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; - -using Avalonia; - -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -[assembly: TestFramework("UITests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UnitTests")] -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] -namespace UITests -{ - - public class AvaloniaUiTestFramework : XunitTestFramework - { - public AvaloniaUiTestFramework(IMessageSink messageSink) - : base(messageSink) - { - } - - protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) - => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); - - private class Executor : XunitTestFrameworkExecutor - { - public Executor( - AssemblyName assemblyName, - ISourceInformationProvider sourceInformationProvider, - IMessageSink diagnosticMessageSink) - : base( - assemblyName, - sourceInformationProvider, - diagnosticMessageSink) - { - - } - - protected override async void RunTestCases(IEnumerable testCases, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - { - executionOptions.SetValue("xunit.execution.DisableParallelization", false); - using var assemblyRunner = new Runner( - TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, - executionOptions); - - await assemblyRunner.RunAsync(); - } - } - - private class Runner : XunitTestAssemblyRunner - { - public Runner( - ITestAssembly testAssembly, - IEnumerable testCases, - IMessageSink diagnosticMessageSink, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - : base( - testAssembly, - testCases, - diagnosticMessageSink, - executionMessageSink, - executionOptions) - { - - } - - public override void Dispose() - { - AvaloniaApp.Stop(); - - base.Dispose(); - } - - protected override void SetupSyncContext(int maxParallelThreads) - { - var tcs = new TaskCompletionSource(); - var thread = new Thread(() => - { - try - { - //AvaloniaApp.RegisterDependencies(); - - AvaloniaApp - .BuildAvaloniaApp() - .AfterSetup(_ => - { - tcs.SetResult(SynchronizationContext.Current!); - }) - .StartWithClassicDesktopLifetime(new string[0]); - } - catch (Exception e) - { - tcs.SetException(e); - } - }) - { - IsBackground = true - }; - - thread.Start(); - - SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); - } - } - } -} diff --git a/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs b/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs deleted file mode 100644 index dd73f3791..000000000 --- a/tests/Avalonia.Svg.Skia.UnitTests/SvgImageReloadTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.Platform; -using Avalonia.Threading; -using Svg.Skia; -using Xunit; - -namespace Avalonia.Svg.Skia.UnitTests; - -public class SvgImageReloadTests -{ - Image test; - private string css = ".Black { fill: #FF0000; }"; - Window window; - [Fact] - public async void SvgImage_ReLoad() - { - SKSvg.CacheOriginalStream = true; - var uri = new Uri($"avares://Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg"); - var assetLoader = new StandardAssetLoader(); // AvaloniaLocator.Current.GetService() - - var svgFile = assetLoader.Open(uri); - - var svgSource = SvgSource.LoadFromStream(svgFile); - var svgImage = new SvgImage() { Source = svgSource }; - - test = new Image(); - test.Source = svgImage; - - window = new Window(); - window.Content = test; - window.Show(); - - var timer = new DispatcherTimer(); - timer.Interval = TimeSpan.FromMilliseconds(10); - timer.Tick += Timer_Tick; - timer.Start(); - - await Task.Delay(10000); - - timer?.Stop(); - window?.Close(); - SKSvg.CacheOriginalStream = false; - } - - - - private void Timer_Tick(object sender, EventArgs e) - { - var image = (SvgImage)test.Source; - (image.CurrentCss, css) = (css, image.CurrentCss); - test.InvalidateVisual(); - } -} From a508a638b08fd7845095ece14d3d71bf4767789e Mon Sep 17 00:00:00 2001 From: Roman Kalachik Date: Thu, 29 Feb 2024 16:58:01 +0300 Subject: [PATCH 5/7] UiTests --- tests/Avalonia.Svg.Skia.UiTests/App.axaml | 7 + tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs | 25 + .../Avalonia.Svg.Skia.UiTests/Assets/Icon.svg | 31 + .../Assets/__tiger.svg | 1978 +++++++++++++++++ .../Avalonia.Svg.Skia.UiTests.csproj | 29 + .../Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs | 33 + .../AvaloniaUiTestFramework.cs | 112 + .../SvgImageReloadTests.cs | 58 + 8 files changed, 2273 insertions(+) create mode 100644 tests/Avalonia.Svg.Skia.UiTests/App.axaml create mode 100644 tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs create mode 100644 tests/Avalonia.Svg.Skia.UiTests/Assets/Icon.svg create mode 100644 tests/Avalonia.Svg.Skia.UiTests/Assets/__tiger.svg create mode 100644 tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.csproj create mode 100644 tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs create mode 100644 tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs create mode 100644 tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs diff --git a/tests/Avalonia.Svg.Skia.UiTests/App.axaml b/tests/Avalonia.Svg.Skia.UiTests/App.axaml new file mode 100644 index 000000000..96409ddcd --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/App.axaml @@ -0,0 +1,7 @@ + + + + + diff --git a/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs b/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs new file mode 100644 index 000000000..9ce4152be --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.Primitives; +using Avalonia.Markup.Xaml; + +namespace UITests +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new Window(); + } + base.OnFrameworkInitializationCompleted(); + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.Svg.Skia.UiTests/Assets/Icon.svg b/tests/Avalonia.Svg.Skia.UiTests/Assets/Icon.svg new file mode 100644 index 000000000..382d97f02 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/Assets/Icon.svg @@ -0,0 +1,31 @@ + + SVG Logo + + + + + + + + + + + + + + + + + + + + + + + SVG + + + + + + diff --git a/tests/Avalonia.Svg.Skia.UiTests/Assets/__tiger.svg b/tests/Avalonia.Svg.Skia.UiTests/Assets/__tiger.svg new file mode 100644 index 000000000..11abcd061 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/Assets/__tiger.svg @@ -0,0 +1,1978 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.csproj b/tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.csproj new file mode 100644 index 000000000..8c97e40f2 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + Library + False + enable + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs new file mode 100644 index 000000000..c6cd1da40 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs @@ -0,0 +1,33 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; + +namespace UITests +{ + public static class AvaloniaApp + { + public static void Stop() + { + var app = GetApp(); + if (app is IDisposable disposable) + { + Dispatcher.UIThread.Post(disposable.Dispose); + } + + if (app != null) + Dispatcher.UIThread.Post(() => app.Shutdown()); + } + + public static Window? GetMainWindow() => GetApp()?.MainWindow; + + public static IClassicDesktopStyleApplicationLifetime? GetApp() => + (IClassicDesktopStyleApplicationLifetime?)Application.Current?.ApplicationLifetime; + + public static AppBuilder BuildAvaloniaApp() => + AppBuilder + .Configure() + .UsePlatformDetect(); + } +} diff --git a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs new file mode 100644 index 000000000..54ca14efc --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Avalonia; + +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +[assembly: TestFramework("UITests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UnitTests")] +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] +namespace UITests +{ + + public class AvaloniaUiTestFramework : XunitTestFramework + { + public AvaloniaUiTestFramework(IMessageSink messageSink) + : base(messageSink) + { + } + + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); + + private class Executor : XunitTestFrameworkExecutor + { + public Executor( + AssemblyName assemblyName, + ISourceInformationProvider sourceInformationProvider, + IMessageSink diagnosticMessageSink) + : base( + assemblyName, + sourceInformationProvider, + diagnosticMessageSink) + { + + } + + protected override async void RunTestCases(IEnumerable testCases, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + { + executionOptions.SetValue("xunit.execution.DisableParallelization", false); + using var assemblyRunner = new Runner( + TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, + executionOptions); + + await assemblyRunner.RunAsync(); + } + } + + private class Runner : XunitTestAssemblyRunner + { + public Runner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + : base( + testAssembly, + testCases, + diagnosticMessageSink, + executionMessageSink, + executionOptions) + { + + } + + public override void Dispose() + { + AvaloniaApp.Stop(); + + base.Dispose(); + } + + protected override void SetupSyncContext(int maxParallelThreads) + { + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + //AvaloniaApp.RegisterDependencies(); + + AvaloniaApp + .BuildAvaloniaApp() + .AfterSetup(_ => + { + tcs.SetResult(SynchronizationContext.Current!); + }) + .StartWithClassicDesktopLifetime(new string[0]); + } + catch (Exception e) + { + tcs.SetException(e); + } + }) + { + IsBackground = true + }; + + thread.Start(); + + SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); + } + } + } +} diff --git a/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs b/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs new file mode 100644 index 000000000..dd73f3791 --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Threading; +using Svg.Skia; +using Xunit; + +namespace Avalonia.Svg.Skia.UnitTests; + +public class SvgImageReloadTests +{ + Image test; + private string css = ".Black { fill: #FF0000; }"; + Window window; + [Fact] + public async void SvgImage_ReLoad() + { + SKSvg.CacheOriginalStream = true; + var uri = new Uri($"avares://Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg"); + var assetLoader = new StandardAssetLoader(); // AvaloniaLocator.Current.GetService() + + var svgFile = assetLoader.Open(uri); + + var svgSource = SvgSource.LoadFromStream(svgFile); + var svgImage = new SvgImage() { Source = svgSource }; + + test = new Image(); + test.Source = svgImage; + + window = new Window(); + window.Content = test; + window.Show(); + + var timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromMilliseconds(10); + timer.Tick += Timer_Tick; + timer.Start(); + + await Task.Delay(10000); + + timer?.Stop(); + window?.Close(); + SKSvg.CacheOriginalStream = false; + } + + + + private void Timer_Tick(object sender, EventArgs e) + { + var image = (SvgImage)test.Source; + (image.CurrentCss, css) = (css, image.CurrentCss); + test.InvalidateVisual(); + } +} From 0449bcb93aa24ae5b63bb5c7dd1daf447802cb32 Mon Sep 17 00:00:00 2001 From: Roman Kalachik Date: Thu, 29 Feb 2024 17:03:37 +0300 Subject: [PATCH 6/7] fixed addembly name --- .../Avalonia.Svg.Skia.UiTests.sln | 31 +++++++++++++++++++ .../AvaloniaUiTestFramework.cs | 2 +- .../SvgImageReloadTests.cs | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.sln diff --git a/tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.sln b/tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.sln new file mode 100644 index 000000000..d706c464a --- /dev/null +++ b/tests/Avalonia.Svg.Skia.UiTests/Avalonia.Svg.Skia.UiTests.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34622.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Svg.Skia.UiTests", "Avalonia.Svg.Skia.UiTests.csproj", "{C8D4CD79-3970-40E9-B561-DB3A184B1CD3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Svg.Skia", "..\..\src\Avalonia.Svg.Skia\Avalonia.Svg.Skia.csproj", "{2BAA6C99-A09D-4D75-AD15-2FC2E3E598A3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C8D4CD79-3970-40E9-B561-DB3A184B1CD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8D4CD79-3970-40E9-B561-DB3A184B1CD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8D4CD79-3970-40E9-B561-DB3A184B1CD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8D4CD79-3970-40E9-B561-DB3A184B1CD3}.Release|Any CPU.Build.0 = Release|Any CPU + {2BAA6C99-A09D-4D75-AD15-2FC2E3E598A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BAA6C99-A09D-4D75-AD15-2FC2E3E598A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BAA6C99-A09D-4D75-AD15-2FC2E3E598A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BAA6C99-A09D-4D75-AD15-2FC2E3E598A3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DFF9854E-0239-420F-AB1D-1C12D9239A1D} + EndGlobalSection +EndGlobal diff --git a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs index 54ca14efc..416f5d43e 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs +++ b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs @@ -10,7 +10,7 @@ using Xunit.Abstractions; using Xunit.Sdk; -[assembly: TestFramework("UITests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UnitTests")] +[assembly: TestFramework("UITests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UiTests")] [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] namespace UITests { diff --git a/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs b/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs index dd73f3791..13a3ab9ae 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs +++ b/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs @@ -20,7 +20,7 @@ public class SvgImageReloadTests public async void SvgImage_ReLoad() { SKSvg.CacheOriginalStream = true; - var uri = new Uri($"avares://Avalonia.Svg.Skia.UnitTests/Assets/__tiger.svg"); + var uri = new Uri($"avares://Avalonia.Svg.Skia.UiTests/Assets/__tiger.svg"); var assetLoader = new StandardAssetLoader(); // AvaloniaLocator.Current.GetService() var svgFile = assetLoader.Open(uri); From 4d77050ed14fe3585f69a0239a7be8910ee67aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Thu, 14 Mar 2024 23:37:39 +0100 Subject: [PATCH 7/7] Refactor --- Svg.Skia.sln | 7 + .../SvgCustomDrawOperation.cs | 12 +- src/Svg.Skia/SKSvg.Model.cs | 13 +- tests/Avalonia.Svg.Skia.UiTests/App.axaml | 3 +- tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs | 38 ++-- .../Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs | 50 +++-- .../AvaloniaUiTestFramework.cs | 191 +++++++++--------- .../SvgImageReloadTests.cs | 34 ++-- 8 files changed, 175 insertions(+), 173 deletions(-) diff --git a/Svg.Skia.sln b/Svg.Skia.sln index 40b73ff86..74200d7bf 100644 --- a/Svg.Skia.sln +++ b/Svg.Skia.sln @@ -113,6 +113,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Svg", "src\Avaloni EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaSvgSkiaStylingSample", "samples\AvaloniaSvgSkiaStylingSample\AvaloniaSvgSkiaStylingSample.csproj", "{8A938DC2-1634-4387-BAB3-69F871D54FB5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Svg.Skia.UiTests", "tests\Avalonia.Svg.Skia.UiTests\Avalonia.Svg.Skia.UiTests.csproj", "{CDAEEF97-F162-468B-9424-6496253C2BFE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -207,6 +209,10 @@ Global {8A938DC2-1634-4387-BAB3-69F871D54FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A938DC2-1634-4387-BAB3-69F871D54FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A938DC2-1634-4387-BAB3-69F871D54FB5}.Release|Any CPU.Build.0 = Release|Any CPU + {CDAEEF97-F162-468B-9424-6496253C2BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDAEEF97-F162-468B-9424-6496253C2BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDAEEF97-F162-468B-9424-6496253C2BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDAEEF97-F162-468B-9424-6496253C2BFE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -238,6 +244,7 @@ Global {3049C672-8A3F-4FE4-9973-515B8323B546} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18} {B742F260-0EC6-4805-AE9F-987818CE3CF4} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18} {8A938DC2-1634-4387-BAB3-69F871D54FB5} = {B65D5B3A-77BE-4AFF-B502-A136B9C932F8} + {CDAEEF97-F162-468B-9424-6496253C2BFE} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {12D5E557-A27B-4FB2-83A3-4AC75B04B22C} diff --git a/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs b/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs index 2774d78f4..8af8d592f 100644 --- a/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs +++ b/src/Avalonia.Svg.Skia/SvgCustomDrawOperation.cs @@ -22,32 +22,38 @@ public void Dispose() public Rect Bounds { get; } - public bool HitTest(Point p) => false; + public bool HitTest(Point p) => true; public bool Equals(ICustomDrawOperation? other) => false; public void Render(ImmediateDrawingContext context) { if (_svg == null) + { return; - + } var leaseFeature = context.TryGetFeature(); if (leaseFeature is null) { return; } + using var lease = leaseFeature.Lease(); var canvas = lease?.SkCanvas; if (canvas is null) { return; } - lock (_svg.Locker) + + lock (_svg.Sync) { var picture = _svg.Picture; if (picture is null) + { return; + } + canvas.Save(); canvas.DrawPicture(picture); canvas.Restore(); diff --git a/src/Svg.Skia/SKSvg.Model.cs b/src/Svg.Skia/SKSvg.Model.cs index 0cdce7c52..02018dccb 100644 --- a/src/Svg.Skia/SKSvg.Model.cs +++ b/src/Svg.Skia/SKSvg.Model.cs @@ -83,6 +83,8 @@ public static void Draw(SkiaSharp.SKCanvas skCanvas, string path, SkiaModel skia private string? _originalPath; private System.IO.Stream? _originalStream; + public object Sync { get; } = new (); + public SKSvgSettings Settings { get; } public IAssetLoader AssetLoader { get; } @@ -95,8 +97,6 @@ public static void Draw(SkiaSharp.SKCanvas skCanvas, string path, SkiaModel skia public virtual SkiaSharp.SKPicture? Picture { get; protected set; } - public object Locker { get; } = new object(); - public SvgParameters? Parameters => _originalParameters; public SKSvg() @@ -183,7 +183,7 @@ public SKSvg() public SkiaSharp.SKPicture? ReLoad(SvgParameters? parameters) { - lock (Locker) + lock (Sync) { if (!CacheOriginalStream) { @@ -251,10 +251,11 @@ public bool Save(string path, SkiaSharp.SKColor background, SkiaSharp.SKEncodedI private void Reset() { - Model = null; - Drawable = null; - lock (Locker) + lock (Sync) { + Model = null; + Drawable = null; + Picture?.Dispose(); Picture = null; } diff --git a/tests/Avalonia.Svg.Skia.UiTests/App.axaml b/tests/Avalonia.Svg.Skia.UiTests/App.axaml index 96409ddcd..cdc00924d 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/App.axaml +++ b/tests/Avalonia.Svg.Skia.UiTests/App.axaml @@ -1,6 +1,7 @@  + x:Class="Avalonia.Svg.Skia.UiTests.App" + RequestedThemeVariant="Light"> diff --git a/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs b/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs index 9ce4152be..33adf71fb 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs +++ b/tests/Avalonia.Svg.Skia.UiTests/App.axaml.cs @@ -1,25 +1,23 @@ -using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls.Primitives; using Avalonia.Markup.Xaml; -namespace UITests +namespace Avalonia.Svg.Skia.UiTests; + +public partial class App : Application { - public partial class App : Application - { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new Window(); + } - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.MainWindow = new Window(); - } - base.OnFrameworkInitializationCompleted(); - } - } -} \ No newline at end of file + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs index c6cd1da40..55922d09c 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs +++ b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaApp.cs @@ -1,33 +1,39 @@ using System; -using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; -namespace UITests +namespace Avalonia.Svg.Skia.UiTests; + +public static class AvaloniaApp { - public static class AvaloniaApp - { - public static void Stop() - { - var app = GetApp(); - if (app is IDisposable disposable) - { - Dispatcher.UIThread.Post(disposable.Dispose); - } + public static void Stop() + { + var app = GetApp(); + + if (app is IDisposable disposable) + { + Dispatcher.UIThread.Post(disposable.Dispose); + } - if (app != null) - Dispatcher.UIThread.Post(() => app.Shutdown()); - } + if (app != null) + { + Dispatcher.UIThread.Post(() => app.Shutdown()); + } + } - public static Window? GetMainWindow() => GetApp()?.MainWindow; + public static Window? GetMainWindow() + { + return GetApp()?.MainWindow; + } - public static IClassicDesktopStyleApplicationLifetime? GetApp() => - (IClassicDesktopStyleApplicationLifetime?)Application.Current?.ApplicationLifetime; + private static IClassicDesktopStyleApplicationLifetime? GetApp() + { + return (IClassicDesktopStyleApplicationLifetime?)Application.Current?.ApplicationLifetime; + } - public static AppBuilder BuildAvaloniaApp() => - AppBuilder - .Configure() - .UsePlatformDetect(); - } + public static AppBuilder BuildAvaloniaApp() => + AppBuilder + .Configure() + .UsePlatformDetect(); } diff --git a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs index 416f5d43e..88f45ebb7 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs +++ b/tests/Avalonia.Svg.Skia.UiTests/AvaloniaUiTestFramework.cs @@ -3,110 +3,101 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; - -using Avalonia; - using Xunit; using Xunit.Abstractions; using Xunit.Sdk; -[assembly: TestFramework("UITests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UiTests")] -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] -namespace UITests -{ - - public class AvaloniaUiTestFramework : XunitTestFramework - { - public AvaloniaUiTestFramework(IMessageSink messageSink) - : base(messageSink) - { - } - - protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) - => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); - - private class Executor : XunitTestFrameworkExecutor - { - public Executor( - AssemblyName assemblyName, - ISourceInformationProvider sourceInformationProvider, - IMessageSink diagnosticMessageSink) - : base( - assemblyName, - sourceInformationProvider, - diagnosticMessageSink) - { - - } - - protected override async void RunTestCases(IEnumerable testCases, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - { - executionOptions.SetValue("xunit.execution.DisableParallelization", false); - using var assemblyRunner = new Runner( - TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, - executionOptions); +[assembly: TestFramework("Avalonia.Svg.Skia.UiTests.AvaloniaUiTestFramework", "Avalonia.Svg.Skia.UiTests")] +[assembly: + CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, + MaxParallelThreads = 1)] - await assemblyRunner.RunAsync(); - } - } +namespace Avalonia.Svg.Skia.UiTests; - private class Runner : XunitTestAssemblyRunner - { - public Runner( - ITestAssembly testAssembly, - IEnumerable testCases, - IMessageSink diagnosticMessageSink, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - : base( - testAssembly, - testCases, - diagnosticMessageSink, - executionMessageSink, - executionOptions) - { - - } - - public override void Dispose() - { - AvaloniaApp.Stop(); - - base.Dispose(); - } - - protected override void SetupSyncContext(int maxParallelThreads) - { - var tcs = new TaskCompletionSource(); - var thread = new Thread(() => - { - try - { - //AvaloniaApp.RegisterDependencies(); - - AvaloniaApp - .BuildAvaloniaApp() - .AfterSetup(_ => - { - tcs.SetResult(SynchronizationContext.Current!); - }) - .StartWithClassicDesktopLifetime(new string[0]); - } - catch (Exception e) - { - tcs.SetException(e); - } - }) - { - IsBackground = true - }; - - thread.Start(); - - SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); - } - } - } +public class AvaloniaUiTestFramework : XunitTestFramework +{ + public AvaloniaUiTestFramework(IMessageSink messageSink) + : base(messageSink) + { + } + + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); + + private class Executor : XunitTestFrameworkExecutor + { + public Executor( + AssemblyName assemblyName, + ISourceInformationProvider sourceInformationProvider, + IMessageSink diagnosticMessageSink) + : base( + assemblyName, + sourceInformationProvider, + diagnosticMessageSink) + { + } + + protected override async void RunTestCases(IEnumerable testCases, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + { + executionOptions.SetValue("xunit.execution.DisableParallelization", false); + using var assemblyRunner = new Runner( + TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, + executionOptions); + + await assemblyRunner.RunAsync(); + } + } + + private class Runner : XunitTestAssemblyRunner + { + public Runner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + : base( + testAssembly, + testCases, + diagnosticMessageSink, + executionMessageSink, + executionOptions) + { + } + + public override void Dispose() + { + AvaloniaApp.Stop(); + + base.Dispose(); + } + + protected override void SetupSyncContext(int maxParallelThreads) + { + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + AvaloniaApp + .BuildAvaloniaApp() + .AfterSetup(_ => + { + tcs.SetResult(SynchronizationContext.Current!); + }) + .StartWithClassicDesktopLifetime(Array.Empty()); + } + catch (Exception e) + { + tcs.SetException(e); + } + }) {IsBackground = true}; + + thread.Start(); + + SynchronizationContext.SetSynchronizationContext(tcs.Task.Result); + } + } } diff --git a/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs b/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs index 13a3ab9ae..c0eadf1d0 100644 --- a/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs +++ b/tests/Avalonia.Svg.Skia.UiTests/SvgImageReloadTests.cs @@ -1,39 +1,33 @@ using System; -using System.Threading; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Media; -using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; using Svg.Skia; using Xunit; -namespace Avalonia.Svg.Skia.UnitTests; +namespace Avalonia.Svg.Skia.UiTests; public class SvgImageReloadTests { - Image test; - private string css = ".Black { fill: #FF0000; }"; - Window window; + private Image _test; + private string _css = ".Black { fill: #FF0000; }"; + private Window _window; + [Fact] public async void SvgImage_ReLoad() { SKSvg.CacheOriginalStream = true; var uri = new Uri($"avares://Avalonia.Svg.Skia.UiTests/Assets/__tiger.svg"); - var assetLoader = new StandardAssetLoader(); // AvaloniaLocator.Current.GetService() - + var assetLoader = new StandardAssetLoader(); var svgFile = assetLoader.Open(uri); - var svgSource = SvgSource.LoadFromStream(svgFile); var svgImage = new SvgImage() { Source = svgSource }; - test = new Image(); - test.Source = svgImage; + _test = new Image {Source = svgImage}; - window = new Window(); - window.Content = test; - window.Show(); + _window = new Window {Content = _test}; + _window.Show(); var timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(10); @@ -43,16 +37,14 @@ public async void SvgImage_ReLoad() await Task.Delay(10000); timer?.Stop(); - window?.Close(); + _window?.Close(); SKSvg.CacheOriginalStream = false; } - - private void Timer_Tick(object sender, EventArgs e) { - var image = (SvgImage)test.Source; - (image.CurrentCss, css) = (css, image.CurrentCss); - test.InvalidateVisual(); + var image = (SvgImage)_test.Source; + (image.CurrentCss, _css) = (_css, image.CurrentCss); + _test.InvalidateVisual(); } }