diff --git a/.gitmodules b/.gitmodules index 0b2349ad42..6e9b10efa4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,9 +5,6 @@ path = src/Avalonia.HtmlRenderer/external url = https://github.com/AvaloniaUI/HTML-Renderer.git branch = perspex-pcl -[submodule "src/Markup/Avalonia.Markup.Xaml/OmniXAML"] - path = src/Markup/Avalonia.Markup.Xaml/OmniXAML - url = https://github.com/AvaloniaUI/OmniXAML.git -[submodule "src/Markup/Avalonia.Markup.Xaml/glass"] - path = src/Markup/Avalonia.Markup.Xaml/glass - url = https://github.com/SuperJMN/glass +[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] + path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github + url = https://github.com/AvaloniaUI/Portable.Xaml.git diff --git a/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject b/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Visuals.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Visuals.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Visuals.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 529cbbb65f..7457a1d5bb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,6 @@ environment: secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package init: -- ps: (New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/appveyor/ci/master/scripts/xamarin-vs2017-151-fixed.targets', "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Microsoft.Common.Targets\ImportAfter\Xamarin.Common.targets") - ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")} install: - if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi @@ -27,7 +26,6 @@ before_build: build_script: - ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration" after_build: -- tools\JetBrains.dotMemoryUnit\tools\dotMemoryUnit.exe -targetExecutable="%xunit20%\xunit.console.x86.exe" -returnTargetExitCode --"tests\Avalonia.LeakTests\bin\Release\Avalonia.LeakTests.dll" - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" - pip install codecov - codecov -f "./artifacts/coverage.xml" diff --git a/build.cake b/build.cake index b3822271d4..6c2e554df4 100644 --- a/build.cake +++ b/build.cake @@ -4,8 +4,8 @@ #addin "nuget:?package=Polly&version=4.2.0" #addin "nuget:?package=NuGet.Core&version=2.12.0" +#tool "nuget:?package=xunit.runner.console&version=2.2.0" #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease" -#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.3.20160517.113140" #tool "JetBrains.ReSharper.CommandLineTools" /////////////////////////////////////////////////////////////////////////////// // TOOLS @@ -119,7 +119,6 @@ Task("Restore-NuGet-Packages") }}) .Execute(()=> { NuGetRestore(parameters.MSBuildSolution, new NuGetRestoreSettings { - ToolPath = "./tools/NuGet.CommandLine/tools/NuGet.exe", ToolTimeout = TimeSpan.FromMinutes(toolTimeout) }); }); @@ -194,6 +193,7 @@ Task("Run-Net-Core-Unit-Tests") Task("Run-Unit-Tests") .IsDependentOn("Run-Net-Core-Unit-Tests") .IsDependentOn("Build") + //.IsDependentOn("Run-Leak-Tests") .WithCriteria(() => !parameters.SkipTests) .Does(() => { @@ -206,13 +206,6 @@ Task("Run-Unit-Tests") .Select(name => MakeAbsolute(File("./tests/" + name + "/bin/" + parameters.DirSuffix + "/" + name + ".dll"))) .ToList(); - if (parameters.IsRunningOnWindows) - { - var leakTests = GetFiles("./tests/Avalonia.LeakTests/bin/" + parameters.DirSuffix + "/*.LeakTests.dll"); - - unitTests.AddRange(leakTests); - } - var toolPath = (parameters.IsPlatformAnyCPU || parameters.IsPlatformX86) ? "./tools/xunit.runner.console/tools/xunit.console.x86.exe" : "./tools/xunit.runner.console/tools/xunit.console.exe"; @@ -365,6 +358,44 @@ Task("Publish-NuGet") Information("Publish-NuGet Task failed, but continuing with next Task..."); }); +Task("Run-Leak-Tests") + .WithCriteria(parameters.IsRunningOnWindows) + .IsDependentOn("Build") + .Does(() => + { + DotNetCoreRestore("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj"); + DotNetBuild("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj", settings => settings.SetConfiguration("Release")); + var report = "tests\\Avalonia.LeakTests\\bin\\Release\\report.xml"; + if(System.IO.File.Exists(report)) + System.IO.File.Delete(report); + var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName="tests\\Avalonia.LeakTests\\toolproject\\bin\\dotMemoryUnit.exe", + Arguments="-targetExecutable=\"tools\\xunit.runner.console\\tools\\xunit.console.x86.exe\" -returnTargetExitCode -- tests\\Avalonia.LeakTests\\bin\\Release\\Avalonia.LeakTests.dll -xml tests\\Avalonia.LeakTests\\bin\\Release\\report.xml ", + UseShellExecute = false, + }); + var st = System.Diagnostics.Stopwatch.StartNew(); + while(!proc.HasExited && !System.IO.File.Exists(report)) + { + if(st.Elapsed.TotalSeconds>60) + { + Error("Timed out, probably a bug in dotMemoryUnit"); + proc.Kill(); + throw new Exception("dotMemory issue"); + } + proc.WaitForExit(100); + } + try{ + proc.Kill(); + }catch{} + var doc = System.Xml.Linq.XDocument.Load(report); + if(doc.Root.Descendants("assembly").Any(x=>x.Attribute("failed").Value.ToString() != "0")) + { + throw new Exception("Tests failed"); + } + + }); + Task("Inspect") .WithCriteria(parameters.IsRunningOnWindows) .IsDependentOn("Restore-NuGet-Packages") @@ -372,7 +403,8 @@ Task("Inspect") { var badIssues = new []{"PossibleNullReferenceException"}; var whitelist = new []{"tests", "src\\android", "src\\ios", - "src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external"}; + "src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external", + "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"}; Information("Running code inspections"); diff --git a/build/Markup.props b/build/Markup.props index 36c4cb28d8..5be0cfbf0c 100644 --- a/build/Markup.props +++ b/build/Markup.props @@ -5,5 +5,10 @@ + + + + + diff --git a/build/SharpDX.props b/build/SharpDX.props index 0eb910e71e..69aa817a01 100644 --- a/build/SharpDX.props +++ b/build/SharpDX.props @@ -1,9 +1,9 @@  - - - - - + + + + + diff --git a/build/UnitTests.NetCore.targets b/build/UnitTests.NetCore.targets index a8886fe028..13bb4ed230 100644 --- a/build/UnitTests.NetCore.targets +++ b/build/UnitTests.NetCore.targets @@ -25,5 +25,4 @@ - \ No newline at end of file diff --git a/build/XUnit.props b/build/XUnit.props index 27e0afc987..d59cee6536 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -12,4 +12,16 @@ + + true + + + + + true + + diff --git a/packages.cake b/packages.cake index 07c77f64fd..0824dc6098 100644 --- a/packages.cake +++ b/packages.cake @@ -8,6 +8,42 @@ public class Packages public string NugetPackagesDir {get; private set;} public string SkiaSharpVersion {get; private set; } public string SkiaSharpLinuxVersion {get; private set; } + public Dictionary>> PackageVersions{get; private set;} + + + + class DependencyBuilder : List + { + Packages _parent; + public DependencyBuilder(Packages parent) + { + _parent = parent; + } + + string GetVersion(string name) + { + return _parent.PackageVersions[name].First().Item1; + } + + + public DependencyBuilder Dep(string name, params string[] fws) + { + if(fws.Length == 0) + Add(new NuSpecDependency() { Id = name, Version = GetVersion(name) }); + foreach(var fw in fws) + Add(new NuSpecDependency() { Id = name, TargetFramework = fw, Version = GetVersion(name) }); + return this; + } + public DependencyBuilder Deps(string[] fws, params string[] deps) + { + foreach(var fw in fws) + foreach(var name in deps) + Add(new NuSpecDependency() { Id = name, TargetFramework = fw, Version = GetVersion(name) }); + return this; + } + } + + //new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" }, public Packages(ICakeContext context, Parameters parameters) { // NUGET NUSPECS @@ -26,7 +62,7 @@ public class Packages // Key: Package Id // Value is Tuple where Item1: Package Version, Item2: The *.csproj/*.props file path. var packageVersions = new Dictionary>>(); - + PackageVersions = packageVersions; System.IO.Directory.EnumerateFiles(((DirectoryPath)context.Directory("./build")).FullPath, "*.props", SearchOption.AllDirectories).ToList().ForEach(fileName => { @@ -150,12 +186,12 @@ public class Packages }; var coreLibrariesFiles = coreLibraries.Select((lib) => { - return (FilePath)context.File(lib[0] + lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard1.1/" + lib[1] + lib[2]); + return (FilePath)context.File(lib[0] + lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard1.3/" + lib[1] + lib[2]); }).ToList(); var coreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => { return new NuSpecContent { - Source = file.FullPath, Target = "lib/netstandard1.1" + Source = file.FullPath, Target = "lib/netstandard1.3" }; }); @@ -195,13 +231,12 @@ public class Packages new NuGetPackSettings() { Id = "Avalonia", - Dependencies = new [] + Dependencies = new DependencyBuilder(this) { new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion }, new NuSpecDependency() { Id = "Splat", Version = SplatVersion }, new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion }, new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion }, - new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion }, //.NET Core new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" }, new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" }, @@ -210,8 +245,11 @@ public class Packages new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion }, new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion }, new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion }, - new NuSpecDependency() { Id = "System.ValueTuple", TargetFramework = "netcoreapp1.0", Version = SystemValueTupleVersion } - }, + } + .Deps(new string[]{null, "netcoreapp1.0"}, + "System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives", + "System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter") + .ToArray(), Files = coreLibrariesNuSpecContent .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform) .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform) @@ -231,9 +269,9 @@ public class Packages }, Files = new [] { - new NuSpecContent { Source = "Avalonia.HtmlRenderer.dll", Target = "lib/netstandard1.1" } + new NuSpecContent { Source = "Avalonia.HtmlRenderer.dll", Target = "lib/netstandard1.3" } }, - BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard1.1"), + BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard1.3"), OutputDirectory = parameters.NugetRoot } }; @@ -347,7 +385,7 @@ public class Packages Files = new [] { new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/Avalonia.Win32.dll", Target = "lib/net45" }, - new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard1.1/Avalonia.Win32.dll", Target = "lib/netstandard1.1" } + new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard1.3/Avalonia.Win32.dll", Target = "lib/netstandard1.3" } }, BasePath = context.Directory("./src/Windows"), OutputDirectory = parameters.NugetRoot @@ -368,9 +406,9 @@ public class Packages }, Files = new [] { - new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/net45" } + new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/netstandard1.3" } }, - BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix), + BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix + "/netstandard1.3"), OutputDirectory = parameters.NugetRoot }, /////////////////////////////////////////////////////////////////////////////// @@ -402,9 +440,9 @@ public class Packages }, Files = new [] { - new NuSpecContent { Source = "Avalonia.Gtk3.dll", Target = "lib/netstandard1.1" } + new NuSpecContent { Source = "Avalonia.Gtk3.dll", Target = "lib/netstandard1.3" } }, - BasePath = context.Directory("./src/Gtk/Avalonia.Gtk3/bin/" + parameters.DirSuffix + "/netstandard1.1"), + BasePath = context.Directory("./src/Gtk/Avalonia.Gtk3/bin/" + parameters.DirSuffix + "/netstandard1.3"), OutputDirectory = parameters.NugetRoot }, /////////////////////////////////////////////////////////////////////////////// @@ -460,9 +498,9 @@ public class Packages new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="net45", Version = parameters.Version }, new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version }, //.NET Core - new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.1", Version = parameters.Version }, - new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="netcoreapp1.1", Version = parameters.Version }, - new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.1", Version = parameters.Version } + new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.0", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="netcoreapp1.0", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.0", Version = parameters.Version } }, Files = new NuSpecContent[] { diff --git a/samples/BindingTest/App.config b/samples/BindingTest/App.config index d9f870f27b..373b3a13f8 100644 --- a/samples/BindingTest/App.config +++ b/samples/BindingTest/App.config @@ -1,21 +1,21 @@ - + - + - - + + - - + + - - + + diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 1c4e4f8169..8e78b0c2cd 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -9,7 +9,7 @@ Properties BindingTest BindingTest - v4.5 + v4.6.1 512 true @@ -162,5 +162,4 @@ - \ No newline at end of file diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index 95f671fd84..7d633bcd0c 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -44,6 +44,8 @@ + + diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index 4b58bf2279..2ec052c258 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -49,6 +49,9 @@ namespace BindingTest.ViewModels Thread.Sleep(1000); } }); + + CurrentTimeObservable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) + .Select(x => DateTimeOffset.Now.ToString()); } public ObservableCollection Items { get; } @@ -85,6 +88,7 @@ namespace BindingTest.ViewModels private set { this.RaiseAndSetIfChanged(ref _currentTime, value); } } + public IObservable CurrentTimeObservable { get; } public ReactiveCommand StringValueCommand { get; } public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 4db67a8b60..71e3c4169c 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -158,5 +158,4 @@ - \ No newline at end of file diff --git a/samples/ControlCatalog.Desktop/App.config b/samples/ControlCatalog.Desktop/App.config index 10a06d8258..68403e421f 100644 --- a/samples/ControlCatalog.Desktop/App.config +++ b/samples/ControlCatalog.Desktop/App.config @@ -1,21 +1,21 @@ - + - + - - + + - - + + - - + + diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index e215105149..a3940a3a7d 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -9,7 +9,7 @@ Properties ControlCatalog.Desktop ControlCatalog.Desktop - v4.5 + v4.6.1 512 true @@ -144,5 +144,4 @@ - \ No newline at end of file diff --git a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj index 01d287ea83..d0c73b2553 100644 --- a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj +++ b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj @@ -181,5 +181,4 @@ - \ No newline at end of file diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 2a9f8f70de..3f978fa7f4 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 False false diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index c7c61e2b4e..413794dfa2 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -10,6 +10,7 @@ namespace ControlCatalog { this.InitializeComponent(); this.AttachDevTools(); + Renderer.DrawDirtyRects = Renderer.DrawFps = true; } private void InitializeComponent() diff --git a/samples/RenderTest/App.config b/samples/RenderTest/App.config index 10a06d8258..68403e421f 100644 --- a/samples/RenderTest/App.config +++ b/samples/RenderTest/App.config @@ -1,21 +1,21 @@ - + - + - - + + - - + + - - + + diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj index cf32316086..95f1479b6b 100644 --- a/samples/RenderTest/RenderTest.csproj +++ b/samples/RenderTest/RenderTest.csproj @@ -9,7 +9,7 @@ Properties RenderTest RenderTest - v4.5 + v4.6.1 512 true @@ -183,5 +183,4 @@ - \ No newline at end of file diff --git a/samples/VirtualizationTest/App.config b/samples/VirtualizationTest/App.config index 10a06d8258..68403e421f 100644 --- a/samples/VirtualizationTest/App.config +++ b/samples/VirtualizationTest/App.config @@ -1,21 +1,21 @@ - + - + - - + + - - + + - - + + diff --git a/samples/VirtualizationTest/VirtualizationTest.csproj b/samples/VirtualizationTest/VirtualizationTest.csproj index 9b9d8a94c6..327e659966 100644 --- a/samples/VirtualizationTest/VirtualizationTest.csproj +++ b/samples/VirtualizationTest/VirtualizationTest.csproj @@ -9,7 +9,7 @@ Properties VirtualizationTest VirtualizationTest - v4.5 + v4.6.1 512 true @@ -158,5 +158,4 @@ - \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index d90a251173..da230d34c6 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -4,8 +4,8 @@ net461 - - + + %(Filename) @@ -31,5 +31,4 @@ - \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs index 7fb650a7a8..d5de5ccb4e 100644 --- a/samples/interop/Direct3DInteropSample/Program.cs +++ b/samples/interop/Direct3DInteropSample/Program.cs @@ -11,7 +11,7 @@ namespace Direct3DInteropSample { static void Main(string[] args) { - AppBuilder.Configure().UseWin32().UseDirect2D1().Start(); + AppBuilder.Configure().UseWin32(deferredRendering: false).UseDirect2D1().Start(); } } } diff --git a/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj b/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj index d9a61064f1..56fa426f3c 100644 --- a/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj +++ b/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj @@ -9,7 +9,7 @@ Properties GtkInteropDemo GtkInteropDemo - v4.6 + v4.6.1 512 true @@ -151,5 +151,4 @@ - \ No newline at end of file diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 1aad43a0ea..2fa3ba30b9 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -9,7 +9,7 @@ Properties WindowsInteropTest WindowsInteropTest - v4.6 + v4.6.1 512 true @@ -186,5 +186,4 @@ - \ No newline at end of file diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index e9b4ad0a6d..7134e7d1b4 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -52,13 +52,11 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToSingleton() .Bind().ToConstant(Instance) - .Bind().ToConstant(ImmediateRenderer.Factory) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() .Bind().ToConstant(Instance) .Bind().ToSingleton() .Bind().ToConstant(new DefaultRenderLoop(60)) - .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); SkiaPlatform.Initialize(); diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 4babdc8bff..091fdcc4b5 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -133,8 +133,4 @@ - - - $(MSBuildToolsPath)\Roslyn - \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 0c62eb9060..6c8db17c71 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -13,6 +13,7 @@ using System.Reactive.Disposables; using Avalonia.Android.Platform.Input; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Rendering; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -85,7 +86,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IPlatformHandle Handle => _view; public IEnumerable Surfaces => new object[] {this}; - + + public IRenderer CreateRenderer(IRenderRoot root) + { + return new ImmediateRenderer(root); + } + public virtual void Hide() { _view.Visibility = ViewStates.Invisible; diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index 826f1f0132..87d1f67986 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -157,5 +157,4 @@ - \ No newline at end of file diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index f02ec2f31c..8f832dabcf 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index cc458545e2..e0d12ce262 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index ace898b55f..a3c3015a38 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -220,7 +220,7 @@ namespace Avalonia.Collections /// /// Removes all items from the collection. /// - public void Clear() + public virtual void Clear() { if (this.Count > 0) { diff --git a/src/Avalonia.Base/Metadata/AmbientAttribute.cs b/src/Avalonia.Base/Metadata/AmbientAttribute.cs new file mode 100644 index 0000000000..db36953300 --- /dev/null +++ b/src/Avalonia.Base/Metadata/AmbientAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Metadata +{ + /// + /// Defines the ambient class/property + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = true)] + public class AmbientAttribute : Attribute + { + } +} diff --git a/src/Avalonia.Base/Metadata/TemplateContent.cs b/src/Avalonia.Base/Metadata/TemplateContent.cs new file mode 100644 index 0000000000..ecc16b04a2 --- /dev/null +++ b/src/Avalonia.Base/Metadata/TemplateContent.cs @@ -0,0 +1,15 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Metadata +{ + /// + /// Defines the property that contains the object's content in markup. + /// + [AttributeUsage(AttributeTargets.Property)] + public class TemplateContentAttribute : Attribute + { + } +} diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 37f348340c..037546b186 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Controls/Classes.cs b/src/Avalonia.Controls/Classes.cs index 02fd6e63bf..c897e86697 100644 --- a/src/Avalonia.Controls/Classes.cs +++ b/src/Avalonia.Controls/Classes.cs @@ -86,6 +86,20 @@ namespace Avalonia.Controls base.AddRange(c); } + /// + /// Remvoes all non-pseudoclasses from the collection. + /// + public override void Clear() + { + for (var i = Count - 1; i >= 0; --i) + { + if (!this[i].StartsWith(":")) + { + RemoveAt(i); + } + } + } + /// /// Inserts a style class into the collection. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 83a76cb1a7..eca5967a58 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -355,18 +355,28 @@ namespace Avalonia.Controls if (--_initCount == 0 && _isAttachedToLogicalTree) { - if (!_styled) - { - RegisterWithNameScope(); - ApplyStyling(); - _styled = true; - } + InitializeStylesIfNeeded(); - if (!IsInitialized) - { - IsInitialized = true; - Initialized?.Invoke(this, EventArgs.Empty); - } + InitializeIfNeeded(); + } + } + + private void InitializeStylesIfNeeded(bool force = false) + { + if (_initCount == 0 && (!_styled || force)) + { + RegisterWithNameScope(); + ApplyStyling(); + _styled = true; + } + } + + private void InitializeIfNeeded() + { + if (_initCount == 0 && !IsInitialized) + { + IsInitialized = true; + Initialized?.Invoke(this, EventArgs.Empty); } } @@ -580,11 +590,7 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); - if (!IsInitialized) - { - IsInitialized = true; - Initialized?.Invoke(this, EventArgs.Empty); - } + InitializeIfNeeded(); } /// @@ -752,12 +758,7 @@ namespace Avalonia.Controls { _isAttachedToLogicalTree = true; - if (_initCount == 0) - { - RegisterWithNameScope(); - ApplyStyling(); - _styled = true; - } + InitializeStylesIfNeeded(true); OnAttachedToLogicalTree(e); } diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 793399841c..3272d3779b 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -64,9 +64,12 @@ namespace Avalonia.Controls { Contract.Requires(value != null); - VisualChildren.Clear(); - _children.Clear(); - _children.AddRange(value); + if (_children != value) + { + VisualChildren.Clear(); + _children.Clear(); + _children.AddRange(value); + } } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index ba3ecb99c5..60e25d2be6 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Rendering; using JetBrains.Annotations; namespace Avalonia.Platform @@ -60,6 +61,12 @@ namespace Avalonia.Platform /// Action ScalingChanged { get; set; } + /// + /// Creates a new renderer for the toplevel. + /// + /// The toplevel. + IRenderer CreateRenderer(IRenderRoot root); + /// /// Invalidates a rect on the toplevel. /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index a0a8f6b27e..f8db0e2a5b 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -90,8 +90,7 @@ namespace Avalonia.Controls _renderInterface = TryGetService(dependencyResolver); var renderLoop = TryGetService(dependencyResolver); - var rendererFactory = TryGetService(dependencyResolver); - Renderer = rendererFactory?.CreateRenderer(this, renderLoop); + Renderer = impl.CreateRenderer(this); impl.SetInputRoot(this); @@ -181,6 +180,9 @@ namespace Avalonia.Controls /// double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; + /// + double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1; + IStyleHost IStyleHost.StylingParent { get { return AvaloniaLocator.Current.GetService(); } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3802f2b6ea..46c625cc4c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -225,8 +225,14 @@ namespace Avalonia.Controls /// public override void Hide() { + if (!IsVisible) + { + return; + } + using (BeginAutoSizing()) { + Renderer?.Stop(); PlatformImpl?.Hide(); } @@ -252,6 +258,7 @@ namespace Avalonia.Controls using (BeginAutoSizing()) { PlatformImpl?.Show(); + Renderer?.Start(); } } @@ -297,6 +304,8 @@ namespace Avalonia.Controls var modal = PlatformImpl?.ShowDialog(); var result = new TaskCompletionSource(); + Renderer?.Start(); + Observable.FromEventPattern( x => this.Closed += x, x => this.Closed -= x) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index fbdf64b14a..bcc09936ba 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -117,6 +117,7 @@ namespace Avalonia.Controls try { + Renderer?.Stop(); PlatformImpl?.Hide(); IsVisible = false; } @@ -145,6 +146,7 @@ namespace Avalonia.Controls } PlatformImpl?.Show(); + Renderer?.Start(); } finally { diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index be0b98b24c..a68e8760f2 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 8d30f3cf25..2f5fc79147 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -5,8 +5,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; -using OmniXaml; -using OmniXaml.ObjectAssembler; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Markup.Xaml; diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index e4752f6662..be3f397283 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index b475c4b3ff..2fbcba40c8 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -9,7 +9,7 @@ Properties Avalonia.DotNetFrameworkRuntime Avalonia.DotNetFrameworkRuntime - v4.5 + v4.6.1 512 @@ -69,5 +69,4 @@ - \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj index b1fc7a4278..f715217e42 100644 --- a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj +++ b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 False False false diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index e9e74e24fe..b5482ebce1 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj index ab9ab88a37..9d22de86b3 100644 --- a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj +++ b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Layout/Avalonia.Layout.csproj b/src/Avalonia.Layout/Avalonia.Layout.csproj index 45a40d2fd4..d0260391d8 100644 --- a/src/Avalonia.Layout/Avalonia.Layout.csproj +++ b/src/Avalonia.Layout/Avalonia.Layout.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj b/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj index 9aa81c19f9..508ede7f7d 100644 --- a/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj +++ b/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 28522d8849..2d66b62eab 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 False false diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index 5965645391..6bf37b522b 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index c050ff0e75..3dfd9118af 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -13,10 +13,10 @@ namespace Avalonia.Styling /// public class Style : IStyle { - private static Dictionary> _applied = + private static Dictionary> _applied = new Dictionary>(); - private Dictionary _resources; + private StyleResources _resources; /// /// Initializes a new instance of the class. @@ -37,13 +37,13 @@ namespace Avalonia.Styling /// /// Gets or sets a dictionary of style resources. /// - public IDictionary Resources + public StyleResources Resources { get { if (_resources == null) { - _resources = new Dictionary(); + _resources = new StyleResources(); } return _resources; @@ -51,11 +51,16 @@ namespace Avalonia.Styling set { + var resources = Resources; - - foreach (var i in value) + if (!Equals(resources, value)) { - resources.Add(i); + foreach (var i in value) + { + resources[i.Key] = i.Value; + //resources.Add(i.Key, i.Value); + //(resources as IDictionary).Add(i); + } } } } @@ -69,7 +74,7 @@ namespace Avalonia.Styling /// Gets or sets the style's setters. /// [Content] - public IEnumerable Setters { get; set; } = new List(); + public IList Setters { get; set; } = new List(); /// /// Attaches the style to a control if the style's selector matches. diff --git a/src/Avalonia.Styling/Styling/StyleResources.cs b/src/Avalonia.Styling/Styling/StyleResources.cs new file mode 100644 index 0000000000..e447c6adfd --- /dev/null +++ b/src/Avalonia.Styling/Styling/StyleResources.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Avalonia.Styling +{ + /// + /// Holds resources for a . + /// + public class StyleResources : IDictionary, IDictionary + { + private Dictionary _inner = new Dictionary(); + + public object this[string key] + { + get { return _inner[key]; } + set { _inner[key] = value; } + } + + public int Count => _inner.Count; + + ICollection IDictionary.Keys => _inner.Keys; + + ICollection IDictionary.Values => _inner.Values; + + bool ICollection>.IsReadOnly => false; + + object IDictionary.this[object key] + { + get { return ((IDictionary)_inner)[key]; } + set { ((IDictionary)_inner)[key] = value; } + } + + ICollection IDictionary.Keys => _inner.Keys; + + ICollection IDictionary.Values => _inner.Values; + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot; + + bool IDictionary.IsFixedSize => false; + + bool IDictionary.IsReadOnly => false; + + public void Add(string key, object value) => _inner.Add(key, value); + + public void Clear() => _inner.Clear(); + + public bool ContainsKey(string key) => _inner.ContainsKey(key); + + public bool Remove(string key) => _inner.Remove(key); + + public IEnumerator> GetEnumerator() => _inner.GetEnumerator(); + + public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value); + + bool ICollection>.Contains(KeyValuePair item) + { + return ((IDictionary)_inner).Contains(item); + } + + void ICollection>.Add(KeyValuePair item) + { + ((IDictionary)_inner).Add(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_inner).CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return ((IDictionary)_inner).Remove(item); + } + + void ICollection.CopyTo(Array array, int index) => ((IDictionary)_inner).CopyTo(array, index); + + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + + IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator(); + + void IDictionary.Add(object key, object value) => ((IDictionary)_inner).Add(key, value); + + bool IDictionary.Contains(object key) => ((IDictionary)_inner).Contains(key); + + void IDictionary.Remove(object key) => ((IDictionary)_inner).Remove(key); + } +} diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index be1d798e2d..4e980680d9 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -1,6 +1,6 @@  - netstandard1.1 + netstandard1.3 false diff --git a/src/Avalonia.Themes.Default/CheckBox.xaml b/src/Avalonia.Themes.Default/CheckBox.xaml index e88e93eac6..2fc19b2679 100644 --- a/src/Avalonia.Themes.Default/CheckBox.xaml +++ b/src/Avalonia.Themes.Default/CheckBox.xaml @@ -1,10 +1,11 @@ +"; + + var styles = AvaloniaXamlLoader.Parse(xaml); + + Assert.Equal(1, styles.Count); + + var style = (Style)styles[0]; + + var setters = style.Setters.Cast().ToArray(); + + Assert.Equal(2, setters.Length); + + Assert.Equal(TextBlock.BackgroundProperty, setters[0].Property); + Assert.Equal(Brushes.White.Color, ((ISolidColorBrush)setters[0].Value).Color); + + Assert.Equal(TextBlock.WidthProperty, setters[1].Property); + Assert.Equal(100.0, setters[1].Value); + } + + [Fact] + public void Style_Setter_With_AttachedProperty_Is_Parsed() + { + var xaml = @" + + +"; + + var styles = AvaloniaXamlLoader.Parse(xaml); + + Assert.Equal(1, styles.Count); + + var style = (Style)styles[0]; + + var setters = style.Setters.Cast().ToArray(); + + Assert.Equal(1, setters.Length); + + Assert.Equal(TextBlock.FontSizeProperty, setters[0].Property); + Assert.Equal(21.0, setters[0].Value); + } + + [Fact] + public void Complex_Style_Is_Parsed() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + +"; + var styles = AvaloniaXamlLoader.Parse(xaml); + + Assert.Equal(1, styles.Count); + + var style = (Style)styles[0]; + + var setters = style.Setters.Cast().ToArray(); + + Assert.Equal(3, setters.Length); + + Assert.Equal(CheckBox.BorderBrushProperty, setters[0].Property); + Assert.Equal(CheckBox.BorderThicknessProperty, setters[1].Property); + Assert.Equal(CheckBox.TemplateProperty, setters[2].Property); + + Assert.IsType(setters[0].Value); + Assert.IsType(setters[1].Value); + Assert.IsType(setters[2].Value); + } + } + + [Fact] + public void Style_Resources_Are_Built() + { + var xaml = @" +"; + + var style = AvaloniaXamlLoader.Parse "; + + var style = AvaloniaXamlLoader.Parse + + +"; + + + var window = AvaloniaXamlLoader.Parse(xaml); + var tracker = (InitializationOrderTracker)window.Content; + + //ensure binding is set and operational first + Assert.Equal(100.0, tracker.Tag); + + Assert.Equal("EndInit 0", tracker.Order.Last()); + } + } + + [Fact] + public void BeginInit_Matches_EndInit() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + + var window = AvaloniaXamlLoader.Parse(xaml); + var tracker = (InitializationOrderTracker)window.Content; + + Assert.Equal(0, tracker.InitState); + } + } + + [Fact] + public void DeferedXamlLoader_Should_Preserve_NamespacesContext() + { + var xaml = +@" + + + + + +"; + + var contentControl = AvaloniaXamlLoader.Parse(xaml); + var template = contentControl.ContentTemplate; + + Assert.NotNull(template); + + var txt = (TextBlock)template.Build(null); + + Assert.Equal((object)NonControl.StringProperty, txt.Tag); + } + + [Fact] + public void Binding_To_List_AvaloniaProperty_Is_Operational() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + + var window = AvaloniaXamlLoader.Parse(xaml); + var listBox = (ListBox)window.Content; + + var vm = new SelectedItemsViewModel() + { + Items = new string[] { "foo", "bar", "baz" } + }; + + window.DataContext = vm; + + Assert.Equal(vm.Items, listBox.Items); + + Assert.Equal(vm.SelectedItems, listBox.SelectedItems); + } + } + + private class SelectedItemsViewModel : INotifyPropertyChanged + { + public string[] Items { get; set; } + + public event PropertyChangedEventHandler PropertyChanged; + + private IList _selectedItems = new AvaloniaList(); + + public IList SelectedItems + { + get { return _selectedItems; } + set + { + _selectedItems = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItems))); + } + } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index a66d6ac6d8..a44d09dee7 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Reactive.Subjects; using Avalonia.Controls; using Avalonia.UnitTests; using Xunit; @@ -83,7 +84,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml [Fact] public void Can_Bind_To_DataContext_Of_Anchor_On_Non_Control() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) { var xaml = @" "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + + var window = AvaloniaXamlLoader.Parse(xaml); var textBlock = (TextBlock)window.Content; - window.ApplyTemplate(); + textBlock.Tag = "foo"; Assert.Equal("foo", textBlock.Text); } @@ -169,7 +171,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml [Fact] public void Longform_Binding_To_Self_Works() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) { var xaml = @" + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = (TextBlock)window.Content; + var observable = new BehaviorSubject("foo"); + + window.DataContext = new { Observable = observable }; + window.ApplyTemplate(); + + Assert.Equal("foo", textBlock.Text); + observable.OnNext("bar"); + Assert.Equal("bar", textBlock.Text); + } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs index bc5745551a..3ecb2d9f37 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs @@ -1,16 +1,18 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System.Collections.Generic; using Avalonia.Controls; using Avalonia.LogicalTree; +using System.Collections.Generic; namespace Avalonia.Markup.Xaml.UnitTests.Xaml { - public class InitializationOrderTracker : Control + public class InitializationOrderTracker : Control, ISupportInitialize { public IList Order { get; } = new List(); + public int InitState { get; private set; } + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { Order.Add("AttachedToLogicalTree"); @@ -22,5 +24,19 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Order.Add($"Property {e.Property.Name} Changed"); base.OnPropertyChanged(e); } + + void ISupportInitialize.BeginInit() + { + ++InitState; + base.BeginInit(); + Order.Add($"BeginInit {InitState}"); + } + + void ISupportInitialize.EndInit() + { + --InitState; + base.EndInit(); + Order.Add($"EndInit {InitState}"); + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/NonControl.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/NonControl.cs index fb000a6db2..7562084072 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/NonControl.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/NonControl.cs @@ -13,6 +13,14 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml public static readonly StyledProperty StringProperty = AvaloniaProperty.Register("String"); + //No getter or setter Avalonia property + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register("Foo"); + + //getter only Avalonia property + public static readonly StyledProperty BarProperty = + AvaloniaProperty.Register("Bar"); + public Control Control { get { return GetValue(ControlProperty); } @@ -24,5 +32,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml get { return GetValue(StringProperty); } set { SetValue(StringProperty, value); } } + + public string Bar + { + get { return GetValue(BarProperty); } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index dc15074445..0c5a89b827 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Xaml.Styling; using Avalonia.Media; using Avalonia.Styling; using Avalonia.UnitTests; @@ -90,7 +91,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml [Fact] public void StyleResource_Can_Be_Assigned_To_Setter() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + //skip default theme and styles, they are not needed + using (UnitTestApplication.Start(TestServices.StyledWindow + .With(theme: () => new Styles()))) { var xaml = @" new Styles()))) + { + var xaml = @" + + + + +"; + + var window = AvaloniaXamlLoader.Parse(xaml); + + Assert.Equal(1, window.Styles.Count); + + var styleInclude = window.Styles[0] as StyleInclude; + + Assert.NotNull(styleInclude); + Assert.NotNull(styleInclude.Source); + Assert.NotNull(styleInclude.Loaded); + } + } + [Fact] public void Setter_Can_Contain_Template() { @@ -239,7 +267,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - + "; var loader = new AvaloniaXamlLoader(); @@ -271,7 +299,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - + "; var loader = new AvaloniaXamlLoader(); @@ -282,4 +310,4 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.csproj b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.csproj index b6c573c3c9..98256529bc 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.csproj +++ b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.csproj @@ -102,5 +102,4 @@ - \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject deleted file mode 100644 index ed3fd080b1..0000000000 --- a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject +++ /dev/null @@ -1,27 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - AbnormalReferenceResolution - \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index 14fb35202e..a1c323ceb0 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -78,6 +78,11 @@ + + + 4.3.0 + + @@ -101,5 +106,4 @@ - \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject deleted file mode 100644 index 30815b1937..0000000000 --- a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems b/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems index e26ac81dda..a3ca0c7493 100644 --- a/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems +++ b/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems @@ -9,7 +9,9 @@ Avalonia.RenderTests + + diff --git a/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj index 9b228a89ee..92111585a7 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj +++ b/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj @@ -102,5 +102,4 @@ - \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.v2.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.v2.ncrunchproject deleted file mode 100644 index e1b4d7cf28..0000000000 --- a/tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - true - false - false - false - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Controls/BorderTests.cs b/tests/Avalonia.RenderTests/Controls/BorderTests.cs index 369f4b3031..ef33ecad8e 100644 --- a/tests/Avalonia.RenderTests/Controls/BorderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/BorderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; @@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } [Fact] - public void Border_1px_Border() + public async Task Border_1px_Border() { Decorator target = new Decorator { @@ -36,12 +37,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Border_2px_Border() + public async Task Border_2px_Border() { Decorator target = new Decorator { @@ -55,12 +56,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Border_Fill() + public async Task Border_Fill() { Decorator target = new Decorator { @@ -73,12 +74,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Border_Brush_Offsets_Content() + public async Task Border_Brush_Offsets_Content() { Decorator target = new Decorator { @@ -96,12 +97,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Border_Padding_Offsets_Content() + public async Task Border_Padding_Offsets_Content() { Decorator target = new Decorator { @@ -120,12 +121,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Border_Margin_Offsets_Content() + public async Task Border_Margin_Offsets_Content() { Decorator target = new Decorator { @@ -144,7 +145,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -153,7 +154,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Centers_Content_Horizontally() + public async Task Border_Centers_Content_Horizontally() { Decorator target = new Decorator { @@ -175,7 +176,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -184,7 +185,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Centers_Content_Vertically() + public async Task Border_Centers_Content_Vertically() { Decorator target = new Decorator { @@ -206,7 +207,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -215,7 +216,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Stretches_Content_Horizontally() + public async Task Border_Stretches_Content_Horizontally() { Decorator target = new Decorator { @@ -237,7 +238,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -246,7 +247,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Stretches_Content_Vertically() + public async Task Border_Stretches_Content_Vertically() { Decorator target = new Decorator { @@ -268,7 +269,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -277,7 +278,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Left_Aligns_Content() + public async Task Border_Left_Aligns_Content() { Decorator target = new Decorator { @@ -299,7 +300,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -308,7 +309,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Right_Aligns_Content() + public async Task Border_Right_Aligns_Content() { Decorator target = new Decorator { @@ -330,7 +331,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -339,7 +340,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Top_Aligns_Content() + public async Task Border_Top_Aligns_Content() { Decorator target = new Decorator { @@ -361,7 +362,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -370,7 +371,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls #else [Fact] #endif - public void Border_Bottom_Aligns_Content() + public async Task Border_Bottom_Aligns_Content() { Decorator target = new Decorator { @@ -392,12 +393,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Border_Nested_Rotate() + public async Task Border_Nested_Rotate() { Decorator target = new Decorator { @@ -420,7 +421,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs new file mode 100644 index 0000000000..e4630d8265 --- /dev/null +++ b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_CAIRO +namespace Avalonia.Cairo.RenderTests.Controls +#elif AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Controls +#endif +{ + public class CustomRenderTests : TestBase + { + public CustomRenderTests() + : base(@"Controls\CustomRender") + { + } + + [Fact] + public async Task Clip() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new CustomRenderer((control, context) => + { + context.FillRectangle( + Brushes.Red, + new Rect(control.Bounds.Size), + 4); + + using (context.PushClip(new Rect(control.Bounds.Size).Deflate(20))) + { + context.FillRectangle( + Brushes.Blue, + new Rect(control.Bounds.Size), + 4); + } + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task GeometryClip() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new CustomRenderer((control, context) => + { + var clip = new EllipseGeometry(new Rect(control.Bounds.Size)); + + context.FillRectangle( + Brushes.Red, + new Rect(control.Bounds.Size), + 4); + + using (context.PushGeometryClip(clip)) + { + context.FillRectangle( + Brushes.Blue, + new Rect(control.Bounds.Size), + 4); + } + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task Opacity() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new CustomRenderer((control, context) => + { + context.FillRectangle( + Brushes.Red, + new Rect(control.Bounds.Size), + 4); + + using (context.PushOpacity(0.5)) + { + context.FillRectangle( + Brushes.Blue, + new Rect(control.Bounds.Size).Deflate(20), + 4); + } + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task OpacityMask() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new CustomRenderer((control, context) => + { + var mask = new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), + EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), + GradientStops = new[] + { + new GradientStop(Color.FromUInt32(0xffffffff), 0), + new GradientStop(Color.FromUInt32(0x00ffffff), 1) + }, + }; + + context.FillRectangle( + Brushes.Red, + new Rect(control.Bounds.Size), + 4); + + using (context.PushOpacityMask(mask, new Rect(control.Bounds.Size))) + { + context.FillRectangle( + Brushes.Blue, + new Rect(control.Bounds.Size).Deflate(20), + 4); + } + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + class CustomRenderer : Control + { + private Action _render; + public CustomRenderer(Action render) => _render = render; + public override void Render(DrawingContext context) => _render(this, context); + } + } +} diff --git a/tests/Avalonia.RenderTests/Controls/ImageTests.cs b/tests/Avalonia.RenderTests/Controls/ImageTests.cs index fcc99398f9..9c1d3624d1 100644 --- a/tests/Avalonia.RenderTests/Controls/ImageTests.cs +++ b/tests/Avalonia.RenderTests/Controls/ImageTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.IO; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -26,7 +27,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } [Fact] - public void Image_Stretch_None() + public async Task Image_Stretch_None() { Decorator target = new Decorator { @@ -44,12 +45,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Image_Stretch_Fill() + public async Task Image_Stretch_Fill() { Decorator target = new Decorator { @@ -67,12 +68,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Image_Stretch_Uniform() + public async Task Image_Stretch_Uniform() { Decorator target = new Decorator { @@ -90,12 +91,12 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Image_Stretch_UniformToFill() + public async Task Image_Stretch_UniformToFill() { Decorator target = new Decorator { @@ -113,7 +114,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/GeometryClippingTests.cs b/tests/Avalonia.RenderTests/GeometryClippingTests.cs index 355606bb43..a1ce8097bd 100644 --- a/tests/Avalonia.RenderTests/GeometryClippingTests.cs +++ b/tests/Avalonia.RenderTests/GeometryClippingTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Text; using Xunit; +using System.Threading.Tasks; #if AVALONIA_CAIRO namespace Avalonia.Cairo.RenderTests @@ -22,10 +23,11 @@ namespace Avalonia.Direct2D1.RenderTests } [Fact] - public void Geometry_Clip_Clips_Path() + public async Task Geometry_Clip_Clips_Path() { var target = new Canvas { + Background = Brushes.Yellow, Clip = StreamGeometry.Parse("F1 M 0,0 H 76 V 76 Z"), Width = 76, Height = 76, @@ -44,7 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index 57f1e40d12..33c14fb975 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -102,7 +102,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png")); } - CompareImages(testName); + CompareImagesNoRenderer(testName); } #if AVALONIA_CAIRO @@ -133,7 +133,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media var name = nameof(WritableBitmapShouldBeUsable) + "_" + fmt; writableBitmap.Save(System.IO.Path.Combine(OutputPath, name + ".out.png")); - CompareImages(name); + CompareImagesNoRenderer(name); } } diff --git a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs index f4e44a81f0..b8f81696b1 100644 --- a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Layout; @@ -34,7 +35,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } [Fact] - public void ImageBrush_Tile_Fill() + public async Task ImageBrush_Tile_Fill() { Decorator target = new Decorator { @@ -53,12 +54,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_Tile_UniformToFill() + public async Task ImageBrush_Tile_UniformToFill() { Decorator target = new Decorator { @@ -77,12 +78,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_Tile_Small_Image() + public async Task ImageBrush_Tile_Small_Image() { Decorator target = new Decorator { @@ -100,12 +101,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_NoStretch_NoTile_Alignment_TopLeft() + public async Task ImageBrush_NoStretch_NoTile_Alignment_TopLeft() { Decorator target = new Decorator { @@ -125,12 +126,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_NoStretch_NoTile_Alignment_Center() + public async Task ImageBrush_NoStretch_NoTile_Alignment_Center() { Decorator target = new Decorator { @@ -150,12 +151,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_NoStretch_NoTile_Alignment_BottomRight() + public async Task ImageBrush_NoStretch_NoTile_Alignment_BottomRight() { Decorator target = new Decorator { @@ -175,7 +176,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } #if AVALONIA_SKIA_SKIP_FAIL @@ -183,7 +184,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_Fill_NoTile() + public async Task ImageBrush_Fill_NoTile() { Decorator target = new Decorator { @@ -201,7 +202,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -210,7 +211,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_Uniform_NoTile() + public async Task ImageBrush_Uniform_NoTile() { Decorator target = new Decorator { @@ -228,7 +229,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -237,7 +238,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_UniformToFill_NoTile() + public async Task ImageBrush_UniformToFill_NoTile() { Decorator target = new Decorator { @@ -255,12 +256,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_NoStretch_NoTile_BottomRightQuarterSource() + public async Task ImageBrush_NoStretch_NoTile_BottomRightQuarterSource() { Decorator target = new Decorator { @@ -279,7 +280,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -288,7 +289,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_NoStretch_NoTile_BottomRightQuarterDest() + public async Task ImageBrush_NoStretch_NoTile_BottomRightQuarterDest() { Decorator target = new Decorator { @@ -307,7 +308,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -316,7 +317,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest() + public async Task ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest() { Decorator target = new Decorator { @@ -336,12 +337,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest() + public async Task ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest() { Decorator target = new Decorator { @@ -361,7 +362,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -370,7 +371,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_NoStretch_FlipX_TopLeftDest() + public async Task ImageBrush_NoStretch_FlipX_TopLeftDest() { Decorator target = new Decorator { @@ -389,7 +390,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -398,7 +399,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void ImageBrush_NoStretch_FlipY_TopLeftDest() + public async Task ImageBrush_NoStretch_FlipY_TopLeftDest() { Decorator target = new Decorator { @@ -417,12 +418,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void ImageBrush_NoStretch_FlipXY_TopLeftDest() + public async Task ImageBrush_NoStretch_FlipXY_TopLeftDest() { Decorator target = new Decorator { @@ -441,7 +442,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs index 6c26d067a7..e7889672b9 100644 --- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void LinearGradientBrush_RedBlue_Horizontal_Fill() + public async Task LinearGradientBrush_RedBlue_Horizontal_Fill() { Decorator target = new Decorator { @@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -60,7 +60,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void LinearGradientBrush_RedBlue_Vertical_Fill() + public async Task LinearGradientBrush_RedBlue_Vertical_Fill() { Decorator target = new Decorator { @@ -82,7 +82,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs new file mode 100644 index 0000000000..383c8ee0f8 --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.Media; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +#if AVALONIA_CAIRO +namespace Avalonia.Cairo.RenderTests.Media +#elif AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class RadialGradientBrushTests : TestBase + { + public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush") + { + } + +#if AVALONIA_SKIA_SKIP_FAIL + [Fact(Skip = "FIXME")] +#else + [Fact] +#endif + public async Task RadialGradientBrush_RedBlue() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new RadialGradientBrush + { + GradientStops = new[] + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs index a50bd1a2c9..9ec901535a 100644 --- a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Layout; @@ -61,7 +62,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } [Fact] - public void VisualBrush_NoStretch_NoTile_Alignment_TopLeft() + public async Task VisualBrush_NoStretch_NoTile_Alignment_TopLeft() { Decorator target = new Decorator { @@ -81,7 +82,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -92,7 +93,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_NoStretch_NoTile_Alignment_Center() + public async Task VisualBrush_NoStretch_NoTile_Alignment_Center() { Decorator target = new Decorator { @@ -112,12 +113,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void VisualBrush_NoStretch_NoTile_Alignment_BottomRight() + public async Task VisualBrush_NoStretch_NoTile_Alignment_BottomRight() { Decorator target = new Decorator { @@ -137,7 +138,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -148,7 +149,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_Fill_NoTile() + public async Task VisualBrush_Fill_NoTile() { Decorator target = new Decorator { @@ -166,7 +167,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -177,7 +178,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_Uniform_NoTile() + public async Task VisualBrush_Uniform_NoTile() { Decorator target = new Decorator { @@ -195,7 +196,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -206,7 +207,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_UniformToFill_NoTile() + public async Task VisualBrush_UniformToFill_NoTile() { Decorator target = new Decorator { @@ -224,12 +225,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void VisualBrush_NoStretch_NoTile_BottomRightQuarterSource() + public async Task VisualBrush_NoStretch_NoTile_BottomRightQuarterSource() { Decorator target = new Decorator { @@ -248,7 +249,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -259,7 +260,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_NoStretch_NoTile_BottomRightQuarterDest() + public async Task VisualBrush_NoStretch_NoTile_BottomRightQuarterDest() { Decorator target = new Decorator { @@ -278,7 +279,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -287,7 +288,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest() + public async Task VisualBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest() { Decorator target = new Decorator { @@ -307,12 +308,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void VisualBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest() + public async Task VisualBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest() { Decorator target = new Decorator { @@ -332,7 +333,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -343,7 +344,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_NoStretch_FlipX_TopLeftDest() + public async Task VisualBrush_NoStretch_FlipX_TopLeftDest() { Decorator target = new Decorator { @@ -362,7 +363,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -373,7 +374,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_NoStretch_FlipY_TopLeftDest() + public async Task VisualBrush_NoStretch_FlipY_TopLeftDest() { Decorator target = new Decorator { @@ -392,7 +393,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -403,7 +404,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media #else [Fact] #endif - public void VisualBrush_NoStretch_FlipXY_TopLeftDest() + public async Task VisualBrush_NoStretch_FlipXY_TopLeftDest() { Decorator target = new Decorator { @@ -422,7 +423,53 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - RenderToFile(target); + await RenderToFile(target); + CompareImages(); + } + +#if AVALONIA_CAIRO + [Fact(Skip = "Font scaling currently broken on cairo")] +#elif AVALONIA_SKIA_SKIP_FAIL + [Fact(Skip = "FIXME")] +#else + [Fact] +#endif + public async Task VisualBrush_InTree_Visual() + { + Border source; + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Grid + { + RowDefinitions = new RowDefinitions("Auto,*"), + Children = + { + (source = new Border + { + Background = Brushes.Yellow, + HorizontalAlignment = HorizontalAlignment.Left, + Child = new TextBlock + { + Text = "Visual" + } + }), + new Border + { + Background = new VisualBrush + { + Stretch = Stretch.Uniform, + Visual = source, + }, + [Grid.RowProperty] = 1, + } + } + } + }; + + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs index b23f231f2e..b49f04f3ee 100644 --- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs +++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Text; using Xunit; +using System.Threading.Tasks; #if AVALONIA_CAIRO namespace Avalonia.Cairo.RenderTests @@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.RenderTests } [Fact] - public void Opacity_Mask_Masks_Element() + public async Task Opacity_Mask_Masks_Element() { var target = new Canvas { @@ -53,7 +54,7 @@ namespace Avalonia.Direct2D1.RenderTests } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Shapes/EllipseTests.cs b/tests/Avalonia.RenderTests/Shapes/EllipseTests.cs index 67cc50cd58..9687e817e3 100644 --- a/tests/Avalonia.RenderTests/Shapes/EllipseTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/EllipseTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } [Fact] - public void Circle_1px_Stroke() + public async Task Circle_1px_Stroke() { Decorator target = new Decorator { @@ -36,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Shapes/LineTests.cs b/tests/Avalonia.RenderTests/Shapes/LineTests.cs index 80a4771c1f..05974e75f1 100644 --- a/tests/Avalonia.RenderTests/Shapes/LineTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/LineTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } [Fact] - public void Line_1px_Stroke() + public async Task Line_1px_Stroke() { Decorator target = new Decorator { @@ -37,12 +38,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Line_1px_Stroke_Reversed() + public async Task Line_1px_Stroke_Reversed() { Decorator target = new Decorator { @@ -57,12 +58,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Line_1px_Stroke_Vertical() + public async Task Line_1px_Stroke_Vertical() { Decorator target = new Decorator { @@ -77,7 +78,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index a9c7004651..0fc3246635 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -15,6 +15,7 @@ namespace Avalonia.Skia.RenderTests namespace Avalonia.Direct2D1.RenderTests.Shapes #endif { + using System.Threading.Tasks; using Avalonia.Collections; public class PathTests : TestBase @@ -29,7 +30,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Line_Absolute() + public async Task Line_Absolute() { Decorator target = new Decorator { @@ -45,7 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -54,7 +55,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Line_Relative() + public async Task Line_Relative() { Decorator target = new Decorator { @@ -70,7 +71,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -79,7 +80,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void HorizontalLine_Absolute() + public async Task HorizontalLine_Absolute() { Decorator target = new Decorator { @@ -95,7 +96,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -104,7 +105,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void HorizontalLine_Relative() + public async Task HorizontalLine_Relative() { Decorator target = new Decorator { @@ -120,7 +121,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -129,7 +130,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void VerticalLine_Absolute() + public async Task VerticalLine_Absolute() { Decorator target = new Decorator { @@ -145,7 +146,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -154,7 +155,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void VerticalLine_Relative() + public async Task VerticalLine_Relative() { Decorator target = new Decorator { @@ -170,7 +171,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -179,7 +180,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void CubicBezier_Absolute() + public async Task CubicBezier_Absolute() { Decorator target = new Decorator { @@ -196,7 +197,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -205,7 +206,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void CubicBezier_Relative() + public async Task CubicBezier_Relative() { Decorator target = new Decorator { @@ -222,7 +223,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -231,7 +232,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Arc_Absolute() + public async Task Arc_Absolute() { Decorator target = new Decorator { @@ -248,7 +249,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -257,7 +258,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Arc_Relative() + public async Task Arc_Relative() { Decorator target = new Decorator { @@ -274,12 +275,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Path_100px_Triangle_Centered() + public async Task Path_100px_Triangle_Centered() { Decorator target = new Decorator { @@ -296,12 +297,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Path_Tick_Scaled() + public async Task Path_Tick_Scaled() { Decorator target = new Decorator { @@ -319,12 +320,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Path_Tick_Scaled_Stroke_8px() + public async Task Path_Tick_Scaled_Stroke_8px() { Decorator target = new Decorator { @@ -342,12 +343,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Path_Expander_With_Border() + public async Task Path_Expander_With_Border() { Decorator target = new Decorator { @@ -370,7 +371,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -381,7 +382,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Path_With_PenLineCap() + public async Task Path_With_PenLineCap() { Decorator target = new Decorator { @@ -401,7 +402,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs b/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs index 0f0debb92d..415f0b4e85 100644 --- a/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PolygonTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -26,7 +27,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Polygon_1px_Stroke() + public async Task Polygon_1px_Stroke() { Decorator target = new Decorator { @@ -43,7 +44,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -52,7 +53,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Polygon_NonUniformFill() + public async Task Polygon_NonUniformFill() { Decorator target = new Decorator { @@ -69,7 +70,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs index d8ebde5712..684529dd4b 100644 --- a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -26,7 +27,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Polyline_1px_Stroke() + public async Task Polyline_1px_Stroke() { var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3), new Point(9, 1), new Point(10, 0), new Point(15, 0) }; @@ -45,7 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } @@ -54,7 +55,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes #else [Fact] #endif - public void Polyline_10px_Stroke_PenLineJoin() + public async Task Polyline_10px_Stroke_PenLineJoin() { var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3), new Point(9, 1), new Point(10, 0), new Point(15, 0) }; @@ -76,7 +77,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/Shapes/RectangleTests.cs b/tests/Avalonia.RenderTests/Shapes/RectangleTests.cs index 1bd102b676..5fa424103f 100644 --- a/tests/Avalonia.RenderTests/Shapes/RectangleTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/RectangleTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } [Fact] - public void Rectangle_1px_Stroke() + public async Task Rectangle_1px_Stroke() { Decorator target = new Decorator { @@ -36,12 +37,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Rectangle_2px_Stroke() + public async Task Rectangle_2px_Stroke() { Decorator target = new Decorator { @@ -55,12 +56,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Rectangle_Stroke_Fill() + public async Task Rectangle_Stroke_Fill() { Decorator target = new Decorator { @@ -75,12 +76,12 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } [Fact] - public void Rectangle_Stroke_Fill_ClipToBounds() + public async Task Rectangle_Stroke_Fill_ClipToBounds() { Decorator target = new Decorator { @@ -96,7 +97,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes } }; - RenderToFile(target); + await RenderToFile(target); CompareImages(); } } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 385cb052da..2c07fcf38f 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -9,6 +9,10 @@ using Avalonia.Media.Imaging; using Avalonia.Rendering; using Xunit; +using Avalonia.Platform; +using System.Threading.Tasks; +using System; +using System.Threading; #if AVALONIA_CAIRO using Avalonia.Cairo; @@ -28,6 +32,9 @@ namespace Avalonia.Direct2D1.RenderTests { public class TestBase { + private static readonly TestThreadingInterface threadingInterface = + new TestThreadingInterface(); + static TestBase() { #if AVALONIA_CAIRO @@ -37,6 +44,10 @@ namespace Avalonia.Direct2D1.RenderTests #else Direct2D1Platform.Initialize(); #endif + AvaloniaLocator.CurrentMutable + .Bind() + .ToConstant(threadingInterface); + } public TestBase(string outputPath) @@ -49,6 +60,8 @@ namespace Avalonia.Direct2D1.RenderTests string testFiles = Path.GetFullPath(@"..\..\tests\TestFiles\Direct2D1"); #endif OutputPath = Path.Combine(testFiles, outputPath); + + threadingInterface.MainThread = Thread.CurrentThread; } public string OutputPath @@ -56,14 +69,16 @@ namespace Avalonia.Direct2D1.RenderTests get; } - protected void RenderToFile(Control target, [CallerMemberName] string testName = "") + protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "") { if (!Directory.Exists(OutputPath)) { Directory.CreateDirectory(OutputPath); } - string path = Path.Combine(OutputPath, testName + ".out.png"); + var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); + var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); + var factory = AvaloniaLocator.Current.GetService(); using (RenderTargetBitmap bitmap = new RenderTargetBitmap( (int)target.Width, @@ -73,24 +88,89 @@ namespace Avalonia.Direct2D1.RenderTests target.Measure(size); target.Arrange(new Rect(size)); bitmap.Render(target); - bitmap.Save(path); + bitmap.Save(immediatePath); + } + + using (var rtb = factory.CreateRenderTargetBitmap((int)target.Width, (int)target.Height, 96, 96)) + using (var renderer = new DeferredRenderer(target, rtb)) + { + Size size = new Size(target.Width, target.Height); + target.Measure(size); + target.Arrange(new Rect(size)); + renderer.UnitTestUpdateScene(); + + // Do the deferred render on a background thread to expose any threading errors in + // the deferred rendering path. + await Task.Run((Action)renderer.UnitTestRender); + + rtb.Save(deferredPath); } } protected void CompareImages([CallerMemberName] string testName = "") { - string expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); - string actualPath = Path.Combine(OutputPath, testName + ".out.png"); - using (MagickImage expected = new MagickImage(expectedPath)) - using (MagickImage actual = new MagickImage(actualPath)) + var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); + var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); + var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); + + using (var expected = new MagickImage(expectedPath)) + using (var immediate = new MagickImage(immediatePath)) + using (var deferred = new MagickImage(deferredPath)) + { + double immediateError = expected.Compare(immediate, ErrorMetric.RootMeanSquared); + double deferredError = expected.Compare(deferred, ErrorMetric.RootMeanSquared); + + if (immediateError > 0.022) + { + Assert.True(false, immediatePath + ": Error = " + immediateError); + } + + if (deferredError > 0.022) + { + Assert.True(false, deferredPath + ": Error = " + deferredError); + } + } + } + + protected void CompareImagesNoRenderer([CallerMemberName] string testName = "") + { + var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); + var actualPath = Path.Combine(OutputPath, testName + ".out.png"); + + using (var expected = new MagickImage(expectedPath)) + using (var actual = new MagickImage(actualPath)) { - double error = expected.Compare(actual, ErrorMetric.RootMeanSquared); + double immediateError = expected.Compare(actual, ErrorMetric.RootMeanSquared); - if (error > 0.022) + if (immediateError > 0.022) { - Assert.True(false, actualPath + ": Error = " + error); + Assert.True(false, actualPath + ": Error = " + immediateError); } } } + + private class TestThreadingInterface : IPlatformThreadingInterface + { + public bool CurrentThreadIsLoopThread => MainThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; + + public Thread MainThread { get; set; } + + public event Action Signaled; + + public void RunLoop(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public void Signal() + { + throw new NotImplementedException(); + } + + public IDisposable StartTimer(TimeSpan interval, Action tick) + { + throw new NotImplementedException(); + } + } } } diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index e30102f35a..a2535e0fb5 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new Dictionary + Resources = new StyleResources { { "Foo", "foo resource" }, { "Bar", "overridden" }, @@ -33,14 +33,14 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new Dictionary + Resources = new StyleResources { { "Bar", "again overridden" }, } }, new Style { - Resources = new Dictionary + Resources = new StyleResources { { "Bar", "bar resource" }, } @@ -64,7 +64,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new Dictionary + Resources = new StyleResources { { "Foo", "foo" }, } diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 938fca8b4a..40023134fd 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -52,5 +52,7 @@ - + + + \ No newline at end of file diff --git a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs new file mode 100644 index 0000000000..4019e65bdf --- /dev/null +++ b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Threading; + +namespace Avalonia.UnitTests +{ + /// + /// Immediately invokes dispatched jobs on the current thread. + /// + public class ImmediateDispatcher : IDispatcher + { + public bool CheckAccess() + { + return true; + } + + public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + action(); + } + + public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + action(); + return Task.FromResult(null); + } + + public void VerifyAccess() + { + } + } +} diff --git a/tests/Avalonia.UnitTests/MockRendererFactory.cs b/tests/Avalonia.UnitTests/MockRendererFactory.cs deleted file mode 100644 index e62012b13c..0000000000 --- a/tests/Avalonia.UnitTests/MockRendererFactory.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Rendering; - -namespace Avalonia.UnitTests -{ - public class MockRendererFactory : IRendererFactory - { - private readonly Func _cb; - - public MockRendererFactory(Func cb = null) - { - _cb = cb; - } - - public MockRendererFactory(IRenderer renderer) : this((_, __) => renderer) - { - - } - - public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) - { - return _cb?.Invoke(root, renderLoop); - } - } -} diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 399870aef9..d64a4ff4ce 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -5,6 +5,7 @@ using System; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; @@ -12,7 +13,7 @@ using Moq; namespace Avalonia.UnitTests { - public class TestRoot : Decorator, IFocusScope, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot + public class TestRoot : Decorator, IFocusScope, ILayoutRoot, IInputRoot, INameScope, IRenderRoot, IStyleRoot { private readonly NameScope _nameScope = new NameScope(); private readonly IRenderTarget _renderTarget = Mock.Of( @@ -20,9 +21,6 @@ namespace Avalonia.UnitTests public TestRoot() { - var rendererFactory = AvaloniaLocator.Current.GetService(); - var renderLoop = AvaloniaLocator.Current.GetService(); - Renderer = rendererFactory?.CreateRenderer(this, renderLoop); } event EventHandler INameScope.Registered @@ -41,18 +39,30 @@ namespace Avalonia.UnitTests public int NameScopeUnregisteredSubscribers { get; private set; } - public Size ClientSize => new Size(100, 100); + public Size ClientSize { get; set; } = new Size(100, 100); public Size MaxClientSize { get; set; } = Size.Infinity; public double LayoutScaling => 1; + public double RenderScaling => 1; + public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService(); public IRenderTarget RenderTarget => null; public IRenderer Renderer { get; set; } + public IAccessKeyHandler AccessKeyHandler => null; + + public IKeyboardNavigationHandler KeyboardNavigationHandler => null; + + public IInputElement PointerOverElement { get; set; } + + public IMouseDevice MouseDevice { get; set; } + + public bool ShowAccessKeys { get; set; } + public IRenderTarget CreateRenderTarget() => _renderTarget; public void Invalidate(Rect rect) diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index f66adec1eb..394dcbaa22 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -23,9 +23,7 @@ namespace Avalonia.UnitTests assetLoader: new AssetLoader(), layoutManager: new LayoutManager(), platform: new AppBuilder().RuntimePlatform, - renderer: (_, __) => Mock.Of(), - renderInterface: CreateRenderInterfaceMock(), - renderLoop: Mock.Of(), + renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), styler: new Styler(), theme: () => CreateDefaultTheme(), @@ -33,7 +31,7 @@ namespace Avalonia.UnitTests windowingPlatform: new MockWindowingPlatform()); public static readonly TestServices MockPlatformRenderInterface = new TestServices( - renderInterface: CreateRenderInterfaceMock()); + renderInterface: new MockPlatformRenderInterface()); public static readonly TestServices MockPlatformWrapper = new TestServices( platform: Mock.Of()); @@ -66,8 +64,8 @@ namespace Avalonia.UnitTests Func keyboardDevice = null, IKeyboardNavigationHandler keyboardNavigation = null, ILayoutManager layoutManager = null, + Func mouseDevice = null, IRuntimePlatform platform = null, - Func renderer = null, IPlatformRenderInterface renderInterface = null, IRenderLoop renderLoop = null, IScheduler scheduler = null, @@ -75,6 +73,7 @@ namespace Avalonia.UnitTests IStyler styler = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, + IWindowImpl windowImpl = null, IWindowingPlatform windowingPlatform = null) { AssetLoader = assetLoader; @@ -83,15 +82,15 @@ namespace Avalonia.UnitTests KeyboardDevice = keyboardDevice; KeyboardNavigation = keyboardNavigation; LayoutManager = layoutManager; + MouseDevice = mouseDevice; Platform = platform; - Renderer = renderer; RenderInterface = renderInterface; - RenderLoop = renderLoop; Scheduler = scheduler; StandardCursorFactory = standardCursorFactory; Styler = styler; Theme = theme; ThreadingInterface = threadingInterface; + WindowImpl = windowImpl; WindowingPlatform = windowingPlatform; } @@ -101,15 +100,15 @@ namespace Avalonia.UnitTests public Func KeyboardDevice { get; } public IKeyboardNavigationHandler KeyboardNavigation { get; } public ILayoutManager LayoutManager { get; } + public Func MouseDevice { get; } public IRuntimePlatform Platform { get; } - public Func Renderer { get; } public IPlatformRenderInterface RenderInterface { get; } - public IRenderLoop RenderLoop { get; } public IScheduler Scheduler { get; } public IStandardCursorFactory StandardCursorFactory { get; } public IStyler Styler { get; } public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } + public IWindowImpl WindowImpl { get; } public IWindowingPlatform WindowingPlatform { get; } public TestServices With( @@ -119,8 +118,8 @@ namespace Avalonia.UnitTests Func keyboardDevice = null, IKeyboardNavigationHandler keyboardNavigation = null, ILayoutManager layoutManager = null, + Func mouseDevice = null, IRuntimePlatform platform = null, - Func renderer = null, IPlatformRenderInterface renderInterface = null, IRenderLoop renderLoop = null, IScheduler scheduler = null, @@ -138,16 +137,16 @@ namespace Avalonia.UnitTests keyboardDevice: keyboardDevice ?? KeyboardDevice, keyboardNavigation: keyboardNavigation ?? KeyboardNavigation, layoutManager: layoutManager ?? LayoutManager, + mouseDevice: mouseDevice ?? MouseDevice, platform: platform ?? Platform, - renderer: renderer ?? Renderer, renderInterface: renderInterface ?? RenderInterface, - renderLoop: renderLoop ?? RenderLoop, scheduler: scheduler ?? Scheduler, standardCursorFactory: standardCursorFactory ?? StandardCursorFactory, styler: styler ?? Styler, theme: theme ?? Theme, threadingInterface: threadingInterface ?? ThreadingInterface, - windowingPlatform: windowingPlatform ?? WindowingPlatform); + windowingPlatform: windowingPlatform ?? WindowingPlatform, + windowImpl: windowImpl ?? WindowImpl); } private static Styles CreateDefaultTheme() diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs index 73c9f370d6..91e797fa87 100644 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs @@ -39,6 +39,8 @@ namespace Avalonia.UnitTests public double LayoutScaling => 1; + public double RenderScaling => 1; + public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService(); public IRenderTarget RenderTarget => null; diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 28577e9670..dfbcdbb9ae 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -51,10 +51,9 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.KeyboardDevice?.Invoke()) .Bind().ToConstant(Services.KeyboardNavigation) .Bind().ToConstant(Services.LayoutManager) + .Bind().ToConstant(Services.MouseDevice?.Invoke()) .Bind().ToConstant(Services.Platform) - .Bind().ToConstant(new RendererFactory(Services.Renderer)) .Bind().ToConstant(Services.RenderInterface) - .Bind().ToConstant(Services.RenderLoop) .Bind().ToConstant(Services.ThreadingInterface) .Bind().ToConstant(Services.Scheduler) .Bind().ToConstant(Services.StandardCursorFactory) @@ -68,20 +67,5 @@ namespace Avalonia.UnitTests Styles.AddRange(styles); } } - - private class RendererFactory : IRendererFactory - { - Func _func; - - public RendererFactory(Func func) - { - _func = func; - } - - public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) - { - return _func?.Invoke(root, renderLoop); - } - } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.v2.ncrunchproject b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.v2.ncrunchproject deleted file mode 100644 index 30815b1937..0000000000 --- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs new file mode 100644 index 0000000000..a9b27ed601 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Threading; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering +{ + public class DeferredRendererTests + { + [Fact] + public void First_Frame_Calls_UpdateScene_On_Dispatcher() + { + var loop = new Mock(); + var root = new TestRoot(); + + var dispatcher = new Mock(); + dispatcher.Setup(x => x.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + .Callback((a, p) => a()); + + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: MockSceneBuilder(root).Object, + layerFactory: MockLayerFactory(root).Object, + dispatcher: dispatcher.Object); + + target.Start(); + RunFrame(loop); + +#if !NETCOREAPP1_1 // Delegate.Method is not available in netcoreapp1.1 + dispatcher.Verify(x => + x.InvokeAsync( + It.Is(a => a.Method.Name == "UpdateScene"), + DispatcherPriority.Render)); +#endif + } + + [Fact] + public void First_Frame_Calls_SceneBuilder_UpdateAll() + { + var loop = new Mock(); + var root = new TestRoot(); + var sceneBuilder = MockSceneBuilder(root); + var dispatcher = new ImmediateDispatcher(); + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder.Object, + layerFactory: MockLayerFactory(root).Object, + dispatcher: dispatcher); + + target.Start(); + RunFrame(loop); + + sceneBuilder.Verify(x => x.UpdateAll(It.IsAny())); + } + + [Fact] + public void Frame_Does_Not_Call_SceneBuilder_If_No_Dirty_Controls() + { + var loop = new Mock(); + var root = new TestRoot(); + var sceneBuilder = MockSceneBuilder(root); + var dispatcher = new ImmediateDispatcher(); + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder.Object, + layerFactory: MockLayerFactory(root).Object, + dispatcher: dispatcher); + + target.Start(); + IgnoreFirstFrame(loop, sceneBuilder); + RunFrame(loop); + + sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()), Times.Never); + sceneBuilder.Verify(x => x.Update(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void Should_Update_Dirty_Controls_In_Order() + { + var loop = new Mock(); + var dispatcher = new ImmediateDispatcher(); + + Border border; + Decorator decorator; + Canvas canvas; + var root = new TestRoot + { + Child = decorator = new Decorator + { + Child = border = new Border + { + Child = canvas = new Canvas() + } + } + }; + + var sceneBuilder = MockSceneBuilder(root); + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder.Object, + layerFactory: MockLayerFactory(root).Object, + dispatcher: dispatcher); + + target.Start(); + IgnoreFirstFrame(loop, sceneBuilder); + target.AddDirty(border); + target.AddDirty(canvas); + target.AddDirty(root); + target.AddDirty(decorator); + + var result = new List(); + sceneBuilder.Setup(x => x.Update(It.IsAny(), It.IsAny())) + .Callback((_, v) => result.Add(v)); + + RunFrame(loop); + + Assert.Equal(new List { root, decorator, border, canvas }, result); + } + + [Fact] + public void Frame_Should_Create_Layer_For_Root() + { + var loop = new Mock(); + var root = new TestRoot(); + var rootLayer = new Mock(); + var dispatcher = new ImmediateDispatcher(); + + var sceneBuilder = new Mock(); + sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) + .Callback(scene => + { + scene.Size = root.ClientSize; + scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); + }); + + var layers = new Mock(); + layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); + + var renderInterface = new Mock(); + + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder.Object, + layerFactory: layers.Object, + dispatcher: dispatcher); + + target.Start(); + RunFrame(loop); + + layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); + } + + [Fact] + public void Should_Create_And_Delete_Layers_For_Transparent_Controls() + { + Border border; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Border + { + Background = Brushes.Red, + Child = border = new Border + { + Background = Brushes.Green, + } + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var loop = new Mock(); + var layerFactory = new MockRenderLayerFactory(new Dictionary + { + { root, CreateLayer() }, + { border, CreateLayer() }, + }); + + var target = new DeferredRenderer( + root, + loop.Object, + layerFactory: layerFactory, + dispatcher: new ImmediateDispatcher()); + root.Renderer = target; + + target.Start(); + RunFrame(loop); + + var rootContext = layerFactory.GetMockDrawingContext(root); + var borderContext = layerFactory.GetMockDrawingContext(border); + + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + + rootContext.ResetCalls(); + borderContext.ResetCalls(); + border.Opacity = 0.5; + RunFrame(loop); + + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); + borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + + rootContext.ResetCalls(); + borderContext.ResetCalls(); + border.Opacity = 1; + RunFrame(loop); + + layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder) + { + RunFrame(loop); + sceneBuilder.ResetCalls(); + } + + private void RunFrame(Mock loop) + { + loop.Raise(x => x.Tick += null, EventArgs.Empty); + } + + private IRenderTargetBitmapImpl CreateLayer() + { + return Mock.Of(x => + x.CreateDrawingContext(It.IsAny()) == Mock.Of()); + } + + private Mock MockLayerFactory(IRenderRoot root) + { + var result = new Mock(); + result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); + return result; + } + + private Mock MockSceneBuilder(IRenderRoot root) + { + var result = new Mock(); + result.Setup(x => x.UpdateAll(It.IsAny())) + .Callback(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize))); + return result; + } + + private class MockRenderLayerFactory : IRenderLayerFactory + { + private IDictionary _layers; + + public MockRenderLayerFactory(IDictionary layers) + { + _layers = layers; + } + + public IRenderTargetBitmapImpl CreateLayer( + IVisual layerRoot, + Size size, + double dpiX, + double dpiY) + { + return _layers[layerRoot]; + } + + public Mock GetMockBitmap(IVisual layerRoot) + { + return Mock.Get(_layers[layerRoot]); + } + + public Mock GetMockDrawingContext(IVisual layerRoot) + { + return Mock.Get(_layers[layerRoot].CreateDrawingContext(null)); + } + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs new file mode 100644 index 0000000000..1044c1f2a3 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -0,0 +1,508 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Linq; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Moq; +using Xunit; +using System; +using Avalonia.Controls.Shapes; + +namespace Avalonia.Visuals.UnitTests.Rendering +{ + public class DeferredRendererTests_HitTesting + { + [Fact] + public void HitTest_Should_Find_Controls_At_Point() + { + using (TestApplication()) + { + var root = new TestRoot + { + Width = 200, + Height = 200, + Child = new Border + { + 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(100, 100), null); + + Assert.Equal(new[] { root.Child }, result); + } + } + + [Fact] + public void HitTest_Should_Not_Find_Empty_Controls_At_Point() + { + using (TestApplication()) + { + var root = new TestRoot + { + Width = 200, + Height = 200, + Child = new Border + { + Width = 100, + Height = 100, + 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(100, 100), null); + + Assert.Empty(result); + } + } + + [Fact] + public void HitTest_Should_Not_Find_Invisible_Controls_At_Point() + { + using (TestApplication()) + { + Border visible; + var root = new TestRoot + { + Width = 200, + Height = 200, + Child = new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + IsVisible = false, + Child = visible = new Border + { + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(100, 100), null); + + Assert.Empty(result); + } + } + + [Fact] + public void HitTest_Should_Not_Find_Control_Outside_Point() + { + using (TestApplication()) + { + var root = new TestRoot + { + Width = 200, + Height = 200, + Child = new Border + { + 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(10, 10), null); + + Assert.Empty(result); + } + } + + [Fact] + public void HitTest_Should_Return_Top_Controls_First() + { + using (TestApplication()) + { + Panel container; + var root = new TestRoot + { + Child = container = new Panel + { + Width = 200, + Height = 200, + Children = new Controls.Controls + { + new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }, + new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(100, 100), null); + + Assert.Equal(new[] { container.Children[1], container.Children[0] }, result); + } + } + + [Fact] + public void HitTest_Should_Return_Top_Controls_First_With_ZIndex() + { + using (TestApplication()) + { + Panel container; + var root = new TestRoot + { + Child = container = new Panel + { + Width = 200, + Height = 200, + Children = new Controls.Controls + { + new Border + { + Width = 100, + Height = 100, + ZIndex = 1, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }, + new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }, + new Border + { + Width = 75, + Height = 75, + ZIndex = 2, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(100, 100), null); + + Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result); + } + } + + [Fact] + public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() + { + using (TestApplication()) + { + Border target; + Panel container; + var root = new TestRoot + { + Child = container = new Panel + { + Width = 200, + Height = 200, + Background = Brushes.Red, + ClipToBounds = false, + Children = new Controls.Controls + { + new Border + { + Width = 100, + Height = 100, + ZIndex = 1, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + Child = target = new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + RenderTransform = new TranslateTransform(110, 110), + } + }, + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(120, 120), null); + + Assert.Equal(new IVisual[] { target, container }, result); + } + } + + [Fact] + public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() + { + using (TestApplication()) + { + Border target; + Panel container; + var root = new TestRoot + { + Child = container = new Panel + { + Width = 100, + Height = 200, + Background = Brushes.Red, + Children = new Controls.Controls + { + new Panel() + { + Width = 100, + Height = 100, + Background = Brushes.Red, + Margin = new Thickness(0, 100, 0, 0), + ClipToBounds = true, + Children = new Controls.Controls + { + (target = new Border() + { + Width = 100, + Height = 100, + Background = Brushes.Red, + Margin = new Thickness(0, -100, 0, 0) + }) + } + } + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(50, 50), null); + + Assert.Equal(new[] { container }, result); + } + } + + [Fact] + public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport() + { + using (TestApplication()) + { + Border target; + Border item1; + Border item2; + ScrollContentPresenter scroll; + Panel container; + var root = new TestRoot + { + Child = container = new Panel + { + Width = 100, + Height = 200, + Background = Brushes.Red, + Children = new Controls.Controls + { + (target = new Border() + { + Name = "b1", + Width = 100, + Height = 100, + Background = Brushes.Red, + }), + new Border() + { + Name = "b2", + Width = 100, + Height = 100, + Background = Brushes.Red, + Margin = new Thickness(0, 100, 0, 0), + Child = scroll = new ScrollContentPresenter() + { + Content = new StackPanel() + { + Children = new Controls.Controls + { + (item1 = new Border() + { + Name = "b3", + Width = 100, + Height = 100, + Background = Brushes.Red, + }), + (item2 = new Border() + { + Name = "b4", + Width = 100, + Height = 100, + Background = Brushes.Red, + }), + } + } + } + } + } + } + }; + + scroll.UpdateChild(); + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(50, 150), null).First(); + + Assert.Equal(item1, result); + + result = root.Renderer.HitTest(new Point(50, 50), null).First(); + + Assert.Equal(target, result); + + scroll.Offset = new Vector(0, 100); + + // We don't have LayoutManager set up so do the layout pass manually. + scroll.Parent.InvalidateArrange(); + container.InvalidateArrange(); + container.Arrange(new Rect(container.DesiredSize)); + + result = root.Renderer.HitTest(new Point(50, 150), null).First(); + Assert.Equal(item2, result); + + result = root.Renderer.HitTest(new Point(50, 50), null).First(); + Assert.Equal(target, result); + } + } + + [Fact] + public void HitTest_Should_Not_Find_Path_When_Outside_Fill() + { + using (TestApplication()) + { + Path path; + var root = new TestRoot + { + Width = 200, + Height = 200, + Child = path = new Path + { + Width = 200, + Height = 200, + Fill = Brushes.Red, + Data = StreamGeometry.Parse("M100,0 L0,100 100,100") + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var context = new DrawingContext(Mock.Of()); + + var result = root.Renderer.HitTest(new Point(100, 100), null); + Assert.Equal(new[] { path }, result); + + result = root.Renderer.HitTest(new Point(10, 10), null); + Assert.Empty(result); + } + } + + [Fact] + public void HitTest_Should_Respect_Geometry_Clip() + { + using (TestApplication()) + { + Border border; + Canvas canvas; + var root = new TestRoot + { + Width = 400, + Height = 400, + Child = border = new Border + { + Background = Brushes.Red, + Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"), + Width = 200, + Height = 200, + Child = canvas = new Canvas + { + Background = Brushes.Yellow, + Margin = new Thickness(10), + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds); + + var context = new DrawingContext(Mock.Of()); + + var result = root.Renderer.HitTest(new Point(200, 200), null); + Assert.Equal(new IVisual[] { canvas, border }, result); + + result = root.Renderer.HitTest(new Point(110, 110), 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 fa53cba938..2c3e9bf11a 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs @@ -35,6 +35,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -72,6 +73,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -101,6 +103,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -145,6 +148,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -199,6 +203,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -257,6 +262,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -305,6 +311,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } }; + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -378,6 +385,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering scroll.UpdateChild(); + root.Renderer = new ImmediateRenderer(root); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -408,10 +416,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering private IDisposable TestApplication() { - return UnitTestApplication.Start( - new TestServices( - renderInterface: new MockPlatformRenderInterface(), - renderer: (root, loop) => new ImmediateRenderer(root))); + return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs new file mode 100644 index 0000000000..1906775783 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class DeferredDrawingContextImplTests + { + [Fact] + public void Should_Add_VisualNode() + { + var parent = new VisualNode(new TestRoot(), null); + var child = new VisualNode(Mock.Of(), null); + var layers = new SceneLayers(parent.Visual); + var target = new DeferredDrawingContextImpl(null, layers); + + target.BeginUpdate(parent); + target.BeginUpdate(child); + + Assert.Equal(1, parent.Children.Count); + Assert.Same(child, parent.Children[0]); + } + + [Fact] + public void Should_Not_Replace_Identical_VisualNode() + { + var parent = new VisualNode(new TestRoot(), null); + var child = new VisualNode(Mock.Of(), null); + var layers = new SceneLayers(parent.Visual); + + parent.AddChild(child); + + var target = new DeferredDrawingContextImpl(null, layers); + + target.BeginUpdate(parent); + target.BeginUpdate(child); + + Assert.Equal(1, parent.Children.Count); + Assert.Same(child, parent.Children[0]); + } + + [Fact] + public void Should_Replace_Different_VisualNode() + { + var parent = new VisualNode(new TestRoot(), null); + var child1 = new VisualNode(Mock.Of(), null); + var child2 = new VisualNode(Mock.Of(), null); + var layers = new SceneLayers(parent.Visual); + + parent.AddChild(child1); + + var target = new DeferredDrawingContextImpl(null, layers); + + target.BeginUpdate(parent); + target.BeginUpdate(child2); + + Assert.Equal(1, parent.Children.Count); + Assert.Same(child2, parent.Children[0]); + } + + [Fact] + public void TrimChildren_Should_Trim_Children() + { + var root = new TestRoot(); + var node = new VisualNode(root, null) { LayerRoot = root }; + + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + + var layers = new SceneLayers(root); + var target = new DeferredDrawingContextImpl(null, layers); + var child1 = new VisualNode(Mock.Of(), null) { LayerRoot = root }; + var child2 = new VisualNode(Mock.Of(), null) { LayerRoot = root }; + + target.BeginUpdate(node); + using (target.BeginUpdate(child1)) { } + using (target.BeginUpdate(child2)) { } + target.TrimChildren(); + + Assert.Equal(2, node.Children.Count); + } + + [Fact] + public void Should_Add_DrawOperations() + { + var node = new VisualNode(new TestRoot(), null); + var layers = new SceneLayers(node.Visual); + var target = new DeferredDrawingContextImpl(null, layers); + + node.LayerRoot = node.Visual; + + using (target.BeginUpdate(node)) + { + target.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100)); + target.DrawRectangle(new Pen(Brushes.Green, 1), new Rect(0, 0, 100, 100)); + } + + Assert.Equal(2, node.DrawOperations.Count); + Assert.IsType(node.DrawOperations[0]); + Assert.IsType(node.DrawOperations[1]); + } + + [Fact] + public void Should_Not_Replace_Identical_DrawOperation() + { + var node = new VisualNode(new TestRoot(), null); + var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); + var layers = new SceneLayers(node.Visual); + var target = new DeferredDrawingContextImpl(null, layers); + + node.LayerRoot = node.Visual; + node.AddDrawOperation(operation); + + using (target.BeginUpdate(node)) + { + target.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100)); + } + + Assert.Equal(1, node.DrawOperations.Count); + Assert.Same(operation, node.DrawOperations.Single()); + + Assert.IsType(node.DrawOperations[0]); + } + + [Fact] + public void Should_Replace_Different_DrawOperation() + { + var node = new VisualNode(new TestRoot(), null); + var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); + var layers = new SceneLayers(node.Visual); + var target = new DeferredDrawingContextImpl(null, layers); + + node.LayerRoot = node.Visual; + node.AddDrawOperation(operation); + + using (target.BeginUpdate(node)) + { + target.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100)); + } + + Assert.Equal(1, node.DrawOperations.Count); + Assert.NotSame(operation, node.DrawOperations.Single()); + + Assert.IsType(node.DrawOperations[0]); + } + + [Fact] + public void Should_Update_DirtyRects() + { + var node = new VisualNode(new TestRoot(), null); + var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); + var layers = new SceneLayers(node.Visual); + var target = new DeferredDrawingContextImpl(null, layers); + + node.LayerRoot = node.Visual; + + using (target.BeginUpdate(node)) + { + target.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100)); + } + + Assert.Equal(new Rect(0, 0, 100, 100), layers.Single().Dirty.Single()); + } + + [Fact] + public void Should_Trim_DrawOperations() + { + var node = new VisualNode(new TestRoot(), null); + + node.LayerRoot = node.Visual; + node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 10, 100), 0)); + node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 20, 100), 0)); + node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 30, 100), 0)); + node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 40, 100), 0)); + + var layers = new SceneLayers(node.Visual); + var target = new DeferredDrawingContextImpl(null, layers); + + using (target.BeginUpdate(node)) + { + target.FillRectangle(Brushes.Green, new Rect(0, 0, 10, 100)); + target.FillRectangle(Brushes.Blue, new Rect(0, 0, 20, 100)); + } + + Assert.Equal(2, node.DrawOperations.Count); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs new file mode 100644 index 0000000000..c51e8941a9 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -0,0 +1,636 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; +using Avalonia.Layout; +using Moq; +using Avalonia.Platform; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public partial class SceneBuilderTests + { + [Fact] + public void Should_Build_Initial_Scene() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + TextBlock textBlock; + var tree = new TestRoot + { + Child = border = new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + Child = textBlock = new TextBlock + { + Text = "Hello World", + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var result = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(result); + + Assert.Same(tree, ((VisualNode)result.Root).LayerRoot); + Assert.Equal(1, result.Root.Children.Count); + + var borderNode = (VisualNode)result.Root.Children[0]; + Assert.Same(borderNode, result.FindNode(border)); + Assert.Same(border, borderNode.Visual); + Assert.Equal(1, borderNode.Children.Count); + Assert.Equal(1, borderNode.DrawOperations.Count); + + var backgroundNode = (RectangleNode)borderNode.DrawOperations[0]; + Assert.Equal(Brushes.Red, backgroundNode.Brush); + + var textBlockNode = (VisualNode)borderNode.Children[0]; + Assert.Same(textBlockNode, result.FindNode(textBlock)); + Assert.Same(textBlock, textBlockNode.Visual); + Assert.Equal(1, textBlockNode.DrawOperations.Count); + + var textNode = (TextNode)textBlockNode.DrawOperations[0]; + Assert.NotNull(textNode.Text); + } + } + + [Fact] + public void Should_Respect_Margin_For_ClipBounds() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Canvas canvas; + var tree = new TestRoot + { + Width = 200, + Height = 300, + Child = new Border + { + Margin = new Thickness(10, 20, 30, 40), + Child = canvas = new Canvas + { + Background = Brushes.AliceBlue, + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var result = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(result); + + var canvasNode = result.FindNode(canvas); + Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); + + // Initial ClipBounds are correct, make sure they're still correct after updating canvas. + result = result.Clone(); + Assert.True(sceneBuilder.Update(result, canvas)); + + canvasNode = result.FindNode(canvas); + Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); + } + } + + [Fact] + public void ClipBounds_Should_Be_Intersection_With_Parent_ClipBounds() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + var tree = new TestRoot + { + Width = 200, + Height = 300, + Child = new Canvas + { + ClipToBounds = true, + Width = 100, + Height = 100, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + Children = + { + (border = new Border + { + Background = Brushes.AliceBlue, + Width = 100, + Height = 100, + [Canvas.LeftProperty] = 50, + [Canvas.TopProperty] = 50, + }) + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var borderNode = scene.FindNode(border); + Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); + + // Initial ClipBounds are correct, make sure they're still correct after updating border. + scene = scene.Clone(); + Assert.True(sceneBuilder.Update(scene, border)); + + borderNode = scene.FindNode(border); + Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); + } + } + + [Fact] + public void Should_Respect_ZIndex() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border front; + Border back; + var tree = new TestRoot + { + Child = new Panel + { + Children = + { + (front = new Border + { + ZIndex = 1, + }), + (back = new Border + { + ZIndex = 0, + }), + } + } + }; + + var result = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(result); + + var panelNode = result.FindNode(tree.Child); + var expected = new IVisual[] { back, front }; + var actual = panelNode.Children.OfType().Select(x => x.Visual).ToArray(); + Assert.Equal(expected, actual); + } + } + + [Fact] + public void ClipBounds_Should_Be_In_Global_Coordinates() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border target; + var tree = new TestRoot + { + Child = new Decorator + { + Margin = new Thickness(24, 26), + Child = target = new Border + { + Margin = new Thickness(26, 24), + Width = 100, + Height = 100, + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var result = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(result); + + var targetNode = result.FindNode(target); + + Assert.Equal(new Rect(50, 50, 100, 100), targetNode.ClipBounds); + } + } + + [Fact] + public void Transform_For_Control_With_RenderTransform_Should_Be_Correct_After_Update() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + var tree = new TestRoot + { + Width = 400, + Height = 200, + Child = new Decorator + { + Width = 200, + Height = 100, + Child = border = new Border + { + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Right, + Width = 100, + RenderTransform = new ScaleTransform(0.5, 1), + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var expectedTransform = Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(225, 50); + var borderNode = scene.FindNode(border); + Assert.Equal(expectedTransform, borderNode.Transform); + + scene = scene.Clone(); + Assert.True(sceneBuilder.Update(scene, border)); + + borderNode = scene.FindNode(border); + Assert.Equal(expectedTransform, borderNode.Transform); + } + } + + [Fact] + public void Should_Update_Border_Background_Node() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + TextBlock textBlock; + var tree = new TestRoot + { + Child = border = new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + Child = textBlock = new TextBlock + { + Foreground = Brushes.Green, + Text = "Hello World", + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var initial = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(initial); + + var initialBackgroundNode = initial.FindNode(border).Children[0]; + var initialTextNode = initial.FindNode(textBlock).DrawOperations[0]; + + Assert.NotNull(initialBackgroundNode); + Assert.NotNull(initialTextNode); + + border.Background = Brushes.Green; + + var result = initial.Clone(); + sceneBuilder.Update(result, border); + + var borderNode = (VisualNode)result.Root.Children[0]; + Assert.Same(border, borderNode.Visual); + + var backgroundNode = (RectangleNode)borderNode.DrawOperations[0]; + Assert.NotSame(initialBackgroundNode, backgroundNode); + Assert.Equal(Brushes.Green, backgroundNode.Brush); + + var textBlockNode = (VisualNode)borderNode.Children[0]; + Assert.Same(textBlock, textBlockNode.Visual); + + var textNode = (TextNode)textBlockNode.DrawOperations[0]; + Assert.Same(initialTextNode, textNode); + } + } + + [Fact] + public void Should_Update_When_Control_Added() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + var tree = new TestRoot + { + Width = 100, + Height = 100, + Child = border = new Border + { + Background = Brushes.Red, + } + }; + + Canvas canvas; + var decorator = new Decorator + { + Child = canvas = new Canvas(), + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var initial = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(initial); + + border.Child = decorator; + var result = initial.Clone(); + + Assert.True(sceneBuilder.Update(result, decorator)); + + // Updating canvas should result in no-op as it should have been updated along + // with decorator as part of the add opeation. + Assert.False(sceneBuilder.Update(result, canvas)); + + var borderNode = (VisualNode)result.Root.Children[0]; + Assert.Equal(1, borderNode.Children.Count); + Assert.Equal(1, borderNode.DrawOperations.Count); + + var decoratorNode = (VisualNode)borderNode.Children[0]; + Assert.Same(decorator, decoratorNode.Visual); + Assert.Same(decoratorNode, result.FindNode(decorator)); + + var canvasNode = (VisualNode)decoratorNode.Children[0]; + Assert.Same(canvas, canvasNode.Visual); + Assert.Same(canvasNode, result.FindNode(canvas)); + } + } + + [Fact] + public void Should_Update_When_Control_Removed() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + Decorator decorator; + Canvas canvas; + var tree = new TestRoot + { + Width = 100, + Height = 100, + Child = border = new Border + { + Background = Brushes.Red, + Child = decorator = new Decorator + { + Child = canvas = new Canvas + { + Background = Brushes.AliceBlue, + } + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var initial = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(initial); + + border.Child = null; + var result = initial.Clone(); + + Assert.True(sceneBuilder.Update(result, decorator)); + Assert.False(sceneBuilder.Update(result, canvas)); + + var borderNode = (VisualNode)result.Root.Children[0]; + Assert.Equal(0, borderNode.Children.Count); + Assert.Equal(1, borderNode.DrawOperations.Count); + + Assert.Null(result.FindNode(decorator)); + Assert.Equal(new Rect(0, 0, 100, 100), result.Layers.Single().Dirty.Single()); + } + } + + [Fact] + public void Should_Update_When_Control_Made_Invisible() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Width = 100, + Height = 100, + Child = decorator = new Decorator + { + Child = border = new Border + { + Background = Brushes.Red, + Child = canvas = new Canvas(), + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var initial = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(initial); + + border.IsVisible = false; + var result = initial.Clone(); + + Assert.True(sceneBuilder.Update(result, border)); + Assert.False(sceneBuilder.Update(result, canvas)); + + var decoratorNode = (VisualNode)result.Root.Children[0]; + Assert.Equal(0, decoratorNode.Children.Count); + + Assert.Null(result.FindNode(border)); + Assert.Null(result.FindNode(canvas)); + Assert.Equal(new Rect(0, 0, 100, 100), result.Layers.Single().Dirty.Single()); + } + } + + [Fact] + public void Should_Update_Descendent_Tranform_When_Margin_Changed() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Width = 100, + Height = 100, + Child = decorator = new Decorator + { + Margin = new Thickness(0, 10, 0, 0), + Child = border = new Border + { + Child = canvas = new Canvas(), + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var borderNode = scene.FindNode(border); + var canvasNode = scene.FindNode(canvas); + Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform); + Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform); + + decorator.Margin = new Thickness(0, 20, 0, 0); + layout.ExecuteLayoutPass(); + + scene = scene.Clone(); + sceneBuilder.Update(scene, decorator); + + borderNode = scene.FindNode(border); + canvasNode = scene.FindNode(canvas); + Assert.Equal(Matrix.CreateTranslation(0, 20), borderNode.Transform); + Assert.Equal(Matrix.CreateTranslation(0, 20), canvasNode.Transform); + } + } + + [Fact] + public void DirtyRects_Should_Contain_Old_And_New_Bounds_When_Margin_Changed() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Width = 100, + Height = 100, + Child = decorator = new Decorator + { + Margin = new Thickness(0, 10, 0, 0), + Child = border = new Border + { + Background = Brushes.Red, + Child = canvas = new Canvas(), + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var borderNode = scene.FindNode(border); + var canvasNode = scene.FindNode(canvas); + Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform); + Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform); + + decorator.Margin = new Thickness(0, 20, 0, 0); + layout.ExecuteLayoutPass(); + + scene = scene.Clone(); + + sceneBuilder.Update(scene, decorator); + + var rects = scene.Layers.Single().Dirty.ToArray(); + Assert.Equal(new[] { new Rect(0, 10, 100, 90) }, rects); + } + } + + [Fact] + public void Resizing_Scene_Should_Add_DirtyRects() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Child = decorator = new Decorator + { + Margin = new Thickness(0, 10, 0, 0), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Child = canvas = new Canvas(), + } + } + }; + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + Assert.Equal(new Size(100, 100), scene.Size); + + tree.ClientSize = new Size(110, 120); + scene = scene.Clone(); + sceneBuilder.Update(scene, tree); + + Assert.Equal(new Size(110, 120), scene.Size); + + var expected = new[] + { + new Rect(100, 0, 10, 100), + new Rect(0, 100, 110, 20), + }; + + Assert.Equal(expected, scene.Layers[tree].Dirty.ToArray()); + Assert.Equal(expected, scene.Layers[border].Dirty.ToArray()); + } + } + + [Fact] + public void Should_Set_GeometryClip() + { + using (TestApplication()) + { + var clip = StreamGeometry.Parse("M100,0 L0,100 100,100"); + Decorator decorator; + var tree = new TestRoot + { + Child = decorator = new Decorator + { + Clip = clip, + } + }; + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var decoratorNode = scene.FindNode(decorator); + Assert.Same(clip.PlatformImpl, decoratorNode.GeometryClip); + } + } + + private IDisposable TestApplication() + { + return UnitTestApplication.Start( + TestServices.MockPlatformRenderInterface.With( + layoutManager: new LayoutManager())); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs new file mode 100644 index 0000000000..13f28018db --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -0,0 +1,304 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; +using Avalonia.Layout; +using Avalonia.Rendering; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public partial class SceneBuilderTests + { + [Fact] + public void Control_With_Transparency_Should_Start_New_Layer() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas(), + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var rootNode = (VisualNode)scene.Root; + var borderNode = (VisualNode)scene.FindNode(border); + var canvasNode = (VisualNode)scene.FindNode(canvas); + + Assert.Same(tree, rootNode.LayerRoot); + Assert.Same(border, borderNode.LayerRoot); + Assert.Same(border, canvasNode.LayerRoot); + Assert.Equal(0.5, scene.Layers[border].Opacity); + + Assert.Equal(2, scene.Layers.Count()); + Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border })); + + border.Opacity = 1; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + rootNode = (VisualNode)scene.Root; + borderNode = (VisualNode)scene.FindNode(border); + canvasNode = (VisualNode)scene.FindNode(canvas); + + Assert.Same(tree, rootNode.LayerRoot); + Assert.Same(tree, borderNode.LayerRoot); + Assert.Same(tree, canvasNode.LayerRoot); + Assert.Equal(1, scene.Layers.Count()); + + var rootDirty = scene.Layers[tree].Dirty; + + Assert.Equal(1, rootDirty.Count()); + Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single()); + } + } + + [Fact] + public void Control_With_OpacityMask_Should_Start_New_Layer() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + OpacityMask = Brushes.Red, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas(), + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var rootNode = (VisualNode)scene.Root; + var borderNode = (VisualNode)scene.FindNode(border); + var canvasNode = (VisualNode)scene.FindNode(canvas); + + Assert.Same(tree, rootNode.LayerRoot); + Assert.Same(border, borderNode.LayerRoot); + Assert.Same(border, canvasNode.LayerRoot); + Assert.Equal(Brushes.Red, scene.Layers[border].OpacityMask); + + Assert.Equal(2, scene.Layers.Count()); + Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border })); + + border.OpacityMask = null; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + rootNode = (VisualNode)scene.Root; + borderNode = (VisualNode)scene.FindNode(border); + canvasNode = (VisualNode)scene.FindNode(canvas); + + Assert.Same(tree, rootNode.LayerRoot); + Assert.Same(tree, borderNode.LayerRoot); + Assert.Same(tree, canvasNode.LayerRoot); + Assert.Equal(1, scene.Layers.Count()); + + var rootDirty = scene.Layers[tree].Dirty; + + Assert.Equal(1, rootDirty.Count()); + Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single()); + } + } + + [Fact] + public void Removing_Transparent_Control_Should_Remove_Layers() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas + { + Opacity = 0.75, + }, + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + Assert.Equal(3, scene.Layers.Count); + + decorator.Child = null; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + Assert.Equal(1, scene.Layers.Count); + } + } + + [Fact] + public void Hiding_Transparent_Control_Should_Remove_Layers() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas + { + Opacity = 0.75, + }, + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + Assert.Equal(3, scene.Layers.Count); + + border.IsVisible = false; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + Assert.Equal(1, scene.Layers.Count); + } + } + + [Fact] + public void GeometryClip_Should_Affect_Child_Layers() + { + using (TestApplication()) + { + var clip = StreamGeometry.Parse("M100,0 L0,100 100,100"); + Decorator decorator; + Border border; + var tree = new TestRoot + { + Child = decorator = new Decorator + { + Clip = clip, + Margin = new Thickness(12, 16), + Child = border = new Border + { + Opacity = 0.5, + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var borderLayer = scene.Layers[border]; + Assert.Equal( + Matrix.CreateTranslation(12, 16), + ((MockStreamGeometryImpl)borderLayer.GeometryClip).Transform); + } + } + + [Fact] + public void Hiding_Root_Should_Not_Remove_Root_Layer() + { + using (TestApplication()) + { + Border border; + var tree = new TestRoot + { + Child = border = new Border() + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + tree.IsVisible = false; + + scene = scene.Clone(); + sceneBuilder.Update(scene, tree); + + Assert.Equal(1, scene.Layers.Count); + } + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs new file mode 100644 index 0000000000..fac6e1ceb6 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class SceneLayersTests + { + [Fact] + public void Layers_Should_Be_Ordered() + { + Border border; + Decorator decorator; + var root = new TestRoot + { + Child = border = new Border + { + Child = decorator = new Decorator(), + } + }; + + var target = new SceneLayers(root); + target.Add(root); + target.Add(decorator); + target.Add(border); + + var result = target.Select(x => x.LayerRoot).ToArray(); + + Assert.Equal(new IVisual[] { root, border, decorator }, result); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs new file mode 100644 index 0000000000..9c0adc432d --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class SceneTests + { + [Fact] + public void Cloning_Scene_Should_Retain_Layers_But_Not_DirtyRects() + { + Decorator decorator; + var tree = new TestRoot + { + Child = decorator = new Decorator(), + }; + + var scene = new Scene(tree); + scene.Layers.Add(tree); + scene.Layers.Add(decorator); + + scene.Layers[tree].Dirty.Add(new Rect(0, 0, 100, 100)); + scene.Layers[decorator].Dirty.Add(new Rect(0, 0, 50, 100)); + + scene = scene.Clone(); + Assert.Equal(2, scene.Layers.Count()); + Assert.Empty(scene.Layers[0].Dirty); + Assert.Empty(scene.Layers[1].Dirty); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs new file mode 100644 index 0000000000..3d2e780e0b --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs @@ -0,0 +1,80 @@ +using System; +using Avalonia.Rendering.SceneGraph; +using Avalonia.VisualTree; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class VisualNodeTests + { + [Fact] + public void Empty_Children_Collections_Should_Be_Shared() + { + var node1 = new VisualNode(Mock.Of(), null); + var node2 = new VisualNode(Mock.Of(), null); + + Assert.Same(node1.Children, node2.Children); + } + + [Fact] + public void Adding_Child_Should_Create_Collection() + { + var node = new VisualNode(Mock.Of(), null); + var collection = node.Children; + + node.AddChild(Mock.Of()); + + Assert.NotSame(collection, node.Children); + } + + [Fact] + public void Empty_DrawOperations_Collections_Should_Be_Shared() + { + var node1 = new VisualNode(Mock.Of(), null); + var node2 = new VisualNode(Mock.Of(), null); + + Assert.Same(node1.DrawOperations, node2.DrawOperations); + } + + [Fact] + public void Adding_DrawOperation_Should_Create_Collection() + { + var node = new VisualNode(Mock.Of(), null); + var collection = node.DrawOperations; + + node.AddDrawOperation(Mock.Of()); + + Assert.NotSame(collection, node.DrawOperations); + } + + [Fact] + public void Cloned_Nodes_Should_Share_DrawOperations_Collection() + { + var node1 = new VisualNode(Mock.Of(), null); + node1.AddDrawOperation(Mock.Of()); + + var node2 = node1.Clone(null); + + Assert.Same(node1.DrawOperations, node2.DrawOperations); + } + + [Fact] + public void Adding_DrawOperation_To_Cloned_Node_Should_Create_New_Collection() + { + var node1 = new VisualNode(Mock.Of(), null); + var operation1 = Mock.Of(); + node1.AddDrawOperation(operation1); + + var node2 = node1.Clone(null); + var operation2 = Mock.Of(); + node2.ReplaceDrawOperation(0, operation2); + + Assert.NotSame(node1.DrawOperations, node2.DrawOperations); + Assert.Equal(1, node1.DrawOperations.Count); + Assert.Equal(1, node2.DrawOperations.Count); + Assert.Same(operation1, node1.DrawOperations[0]); + Assert.Same(operation2, node2.DrawOperations[0]); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/Utilities/TileBrushCalculatorTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/Utilities/TileBrushCalculatorTests.cs new file mode 100644 index 0000000000..8d70b45842 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/Utilities/TileBrushCalculatorTests.cs @@ -0,0 +1,139 @@ +using System; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering.Utilities; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.Utilities +{ + public class TileBrushCalculatorTests + { + [Fact] + public void NoTile_Fill_1x() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Fill, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(100, 100)); + + Assert.False(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 100, 100), result.DestinationRect); + } + + [Fact] + public void NoTile_Fill_2x() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Fill, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(200, 200)); + + // TODO: This doesn't need an intermediate render target. + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 200, 200), result.DestinationRect); + } + + [Fact] + public void NoTile_Uniform_CenterHoriz() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Uniform, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(200, 100)); + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 200, 100), result.DestinationRect); + Assert.Equal(new Size(200, 100), result.IntermediateSize); + Assert.Equal(Matrix.CreateTranslation(50, 0), result.IntermediateTransform); + } + + [Fact] + public void NoTile_Uniform_CenterVert() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Uniform, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(100, 200)); + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 100, 200), result.DestinationRect); + Assert.Equal(new Size(100, 200), result.IntermediateSize); + Assert.Equal(Matrix.CreateTranslation(0, 50), result.IntermediateTransform); + } + + [Fact] + public void NoTile_NoStretch_BottomRightQuarterDest() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.None, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), + new Size(800, 800), + new Size(400, 400)); + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 800, 800), result.SourceRect); + Assert.Equal(new Rect(200, 200, 200, 200), result.DestinationRect); + Assert.Equal(new Size(400, 400), result.IntermediateSize); + Assert.Equal(new Rect(200, 200, 200, 200), result.IntermediateClip); + Assert.Equal(Matrix.CreateTranslation(-100, -100), result.IntermediateTransform); + } + + [Fact] + public void Tile_NoStretch_BottomRightQuarterSource_CenterQuarterDest() + { + var result = new TileBrushCalculator( + TileMode.Tile, + Stretch.None, + AlignmentX.Center, + AlignmentY.Center, + new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), + new RelativeRect(0.25, 0.25, 0.5, 0.5, RelativeUnit.Relative), + new Size(800, 800), + new Size(400, 400)); + + var b = new VisualBrush + { + TileMode = TileMode.Tile, + Stretch = Stretch.None, + SourceRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), + DestinationRect = new RelativeRect(0.25, 0.25, 0.5, 0.5, RelativeUnit.Relative), + Visual = new Border { Width = 400, Height = 400 }, + }; + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(400, 400, 400, 400), result.SourceRect); + Assert.Equal(new Rect(100, 100, 200, 200), result.DestinationRect); + Assert.Equal(new Size(200, 200), result.IntermediateSize); + Assert.Equal(new Rect(0, 0, 200, 200), result.IntermediateClip); + Assert.Equal(Matrix.CreateTranslation(-500, -500), result.IntermediateTransform); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs index ec5f9a8c13..f20c59fabe 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs @@ -152,34 +152,32 @@ namespace Avalonia.Visuals.UnitTests public void Attaching_To_Visual_Tree_Should_Invalidate_Visual() { var renderer = new Mock(); - - using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object))) + var child = new Decorator(); + var root = new TestRoot { - var child = new Decorator(); - var root = new TestRoot(); + Renderer = renderer.Object, + }; - root.Child = child; + root.Child = child; - renderer.Verify(x => x.AddDirty(child)); - } + renderer.Verify(x => x.AddDirty(child)); } [Fact] public void Detaching_From_Visual_Tree_Should_Invalidate_Visual() { var renderer = new Mock(); - - using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object))) + var child = new Decorator(); + var root = new TestRoot { - var child = new Decorator(); - var root = new TestRoot(); + Renderer = renderer.Object, + }; - root.Child = child; - renderer.ResetCalls(); - root.Child = null; + root.Child = child; + renderer.ResetCalls(); + root.Child = null; - renderer.Verify(x => x.AddDirty(child)); - } + renderer.Verify(x => x.AddDirty(child)); } [Fact] diff --git a/tests/TestFiles/Cairo/Controls/CustomRender/Clip.expected.png b/tests/TestFiles/Cairo/Controls/CustomRender/Clip.expected.png new file mode 100644 index 0000000000..c64045d17e Binary files /dev/null and b/tests/TestFiles/Cairo/Controls/CustomRender/Clip.expected.png differ diff --git a/tests/TestFiles/Cairo/Controls/CustomRender/GeometryClip.expected.png b/tests/TestFiles/Cairo/Controls/CustomRender/GeometryClip.expected.png new file mode 100644 index 0000000000..bb39bf62ca Binary files /dev/null and b/tests/TestFiles/Cairo/Controls/CustomRender/GeometryClip.expected.png differ diff --git a/tests/TestFiles/Cairo/Controls/CustomRender/Opacity.expected.png b/tests/TestFiles/Cairo/Controls/CustomRender/Opacity.expected.png new file mode 100644 index 0000000000..66d12819f4 Binary files /dev/null and b/tests/TestFiles/Cairo/Controls/CustomRender/Opacity.expected.png differ diff --git a/tests/TestFiles/Cairo/Controls/CustomRender/OpacityMask.expected.png b/tests/TestFiles/Cairo/Controls/CustomRender/OpacityMask.expected.png new file mode 100644 index 0000000000..212a4f1eaa Binary files /dev/null and b/tests/TestFiles/Cairo/Controls/CustomRender/OpacityMask.expected.png differ diff --git a/tests/TestFiles/Cairo/GeometryClipping/Geometry_Clip_Clips_Path.expected.png b/tests/TestFiles/Cairo/GeometryClipping/Geometry_Clip_Clips_Path.expected.png index 892899507b..1218293ab3 100644 Binary files a/tests/TestFiles/Cairo/GeometryClipping/Geometry_Clip_Clips_Path.expected.png and b/tests/TestFiles/Cairo/GeometryClipping/Geometry_Clip_Clips_Path.expected.png differ diff --git a/tests/TestFiles/Cairo/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png b/tests/TestFiles/Cairo/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png new file mode 100644 index 0000000000..871a90caf3 Binary files /dev/null and b/tests/TestFiles/Cairo/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Controls/CustomRender/Clip.expected.png b/tests/TestFiles/Direct2D1/Controls/CustomRender/Clip.expected.png new file mode 100644 index 0000000000..c64045d17e Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/CustomRender/Clip.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Controls/CustomRender/GeometryClip.expected.png b/tests/TestFiles/Direct2D1/Controls/CustomRender/GeometryClip.expected.png new file mode 100644 index 0000000000..bb39bf62ca Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/CustomRender/GeometryClip.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Controls/CustomRender/Opacity.expected.png b/tests/TestFiles/Direct2D1/Controls/CustomRender/Opacity.expected.png new file mode 100644 index 0000000000..66d12819f4 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/CustomRender/Opacity.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Controls/CustomRender/OpacityMask.expected.png b/tests/TestFiles/Direct2D1/Controls/CustomRender/OpacityMask.expected.png new file mode 100644 index 0000000000..212a4f1eaa Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/CustomRender/OpacityMask.expected.png differ diff --git a/tests/TestFiles/Direct2D1/GeometryClipping/Geometry_Clip_Clips_Path.expected.png b/tests/TestFiles/Direct2D1/GeometryClipping/Geometry_Clip_Clips_Path.expected.png index 892899507b..2e5a1ff6a7 100644 Binary files a/tests/TestFiles/Direct2D1/GeometryClipping/Geometry_Clip_Clips_Path.expected.png and b/tests/TestFiles/Direct2D1/GeometryClipping/Geometry_Clip_Clips_Path.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png new file mode 100644 index 0000000000..fce116d98d Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_InTree_Visual.expected.png b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_InTree_Visual.expected.png new file mode 100644 index 0000000000..3725f2acbc Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_InTree_Visual.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/CustomRender/Clip.expected.png b/tests/TestFiles/Skia/Controls/CustomRender/Clip.expected.png new file mode 100644 index 0000000000..c64045d17e Binary files /dev/null and b/tests/TestFiles/Skia/Controls/CustomRender/Clip.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png b/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png new file mode 100644 index 0000000000..535d411cc7 Binary files /dev/null and b/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/CustomRender/Opacity.expected.png b/tests/TestFiles/Skia/Controls/CustomRender/Opacity.expected.png new file mode 100644 index 0000000000..66d12819f4 Binary files /dev/null and b/tests/TestFiles/Skia/Controls/CustomRender/Opacity.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/CustomRender/OpacityMask.expected.png b/tests/TestFiles/Skia/Controls/CustomRender/OpacityMask.expected.png new file mode 100644 index 0000000000..212a4f1eaa Binary files /dev/null and b/tests/TestFiles/Skia/Controls/CustomRender/OpacityMask.expected.png differ diff --git a/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png b/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png index 892899507b..5993088a33 100644 Binary files a/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png and b/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png differ