diff --git a/Avalonia.sln b/Avalonia.sln index f65ca0b54e..7a0ae0ce53 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -171,6 +171,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GtkInteropDemo", "samples\i EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}" EndProject Global @@ -2363,6 +2365,46 @@ Global {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|Mono.Build.0 = Release|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|x86.ActiveCfg = Release|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|x86.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Any CPU.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhone.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Mono.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Mono.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|x86.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|x86.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhone.Build.0 = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Mono.ActiveCfg = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Mono.Build.0 = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|x86.ActiveCfg = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|x86.Build.0 = Debug|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Any CPU.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhone.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhone.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Mono.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Mono.Build.0 = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|x86.ActiveCfg = Release|Any CPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|x86.Build.0 = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU @@ -2474,6 +2516,7 @@ Global {A0CC0258-D18C-4AB3-854F-7101680FC3F9} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098} {29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection EndGlobal diff --git a/samples/RenderTest/App.config b/samples/RenderTest/App.config new file mode 100644 index 0000000000..2231c71e63 --- /dev/null +++ b/samples/RenderTest/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/samples/RenderTest/App.xaml b/samples/RenderTest/App.xaml new file mode 100644 index 0000000000..d9630eef58 --- /dev/null +++ b/samples/RenderTest/App.xaml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/RenderTest/App.xaml.cs b/samples/RenderTest/App.xaml.cs new file mode 100644 index 0000000000..4492e5ae4c --- /dev/null +++ b/samples/RenderTest/App.xaml.cs @@ -0,0 +1,16 @@ +// 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; +using Avalonia.Markup.Xaml; + +namespace RenderTest +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml new file mode 100644 index 0000000000..ab233fd90b --- /dev/null +++ b/samples/RenderTest/MainWindow.xaml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/samples/RenderTest/MainWindow.xaml.cs b/samples/RenderTest/MainWindow.xaml.cs new file mode 100644 index 0000000000..feb121186f --- /dev/null +++ b/samples/RenderTest/MainWindow.xaml.cs @@ -0,0 +1,82 @@ +// 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.Reactive.Linq; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Avalonia.Rendering; + +namespace RenderTest +{ + public class MainWindow : Window + { + public MainWindow() + { + this.InitializeComponent(); + this.CreateAnimations(); + this.AttachDevTools(); + RendererMixin.DrawFpsCounter = true; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void CreateAnimations() + { + const int Count = 100; + var panel = new WrapPanel(); + + for (var i = 0; i < Count; ++i) + { + var element = new Panel + { + Children = + { + new Ellipse + { + Width = 100, + Height = 100, + Fill = Brushes.Blue, + }, + new Path + { + Data = StreamGeometry.Parse( + "F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"), + Fill = Brushes.Green, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + RenderTransform = new ScaleTransform(2, 2), + } + }, + Margin = new Thickness(4), + RenderTransform = new ScaleTransform(), + }; + + var start = Animate.Stopwatch.Elapsed; + var index = i; + var degrees = Animate.Timer + .Select(x => (x - start).TotalSeconds) + .Where(x => (x % Count) >= index && (x % Count) < index + 1) + .Select(x => (x % 1) / 1); + + element.RenderTransform.Bind( + ScaleTransform.ScaleXProperty, + degrees, + BindingPriority.Animation); + + panel.Children.Add(element); + } + + Content = panel; + } + } +} diff --git a/samples/RenderTest/Program.cs b/samples/RenderTest/Program.cs new file mode 100644 index 0000000000..7a23e09dd4 --- /dev/null +++ b/samples/RenderTest/Program.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Logging.Serilog; +using Avalonia.Platform; +using Serilog; + +namespace RenderTest +{ + internal class Program + { + static void Main(string[] args) + { + InitializeLogging(); + + // TODO: Make this work with GTK/Skia/Cairo depending on command-line args + // again. + AppBuilder.Configure() + .UsePlatformDetect() + .Start(); + } + + // This will be made into a runtime configuration extension soon! + private static void InitializeLogging() + { +#if DEBUG + SerilogLogger.Initialize(new LoggerConfiguration() + .MinimumLevel.Warning() + .WriteTo.Trace(outputTemplate: "{Area}: {Message}") + .CreateLogger()); +#endif + } + } +} diff --git a/samples/RenderTest/Properties/AssemblyInfo.cs b/samples/RenderTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f66c158075 --- /dev/null +++ b/samples/RenderTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RenderTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RenderTest")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f1fdc5b0-4654-416f-ae69-e3e9bbd87801")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj new file mode 100644 index 0000000000..49ce4603ce --- /dev/null +++ b/samples/RenderTest/RenderTest.csproj @@ -0,0 +1,192 @@ + + + + + Debug + AnyCPU + {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} + WinExe + Properties + RenderTest + RenderTest + v4.5 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll + True + + + ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll + True + + + + + ..\..\packages\System.Reactive.Core.3.0.0\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\System.Reactive.Interfaces.3.0.0\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\System.Reactive.Linq.3.0.0\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\System.Reactive.PlatformServices.3.0.0\lib\net45\System.Reactive.PlatformServices.dll + True + + + ..\..\packages\System.Reactive.Windows.Threading.3.0.0\lib\net45\System.Reactive.Windows.Threading.dll + True + + + + + + + + + + + + App.xaml + + + + + MainWindow.xaml + + + + + + + + + Designer + + + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Avalonia.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Avalonia.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Avalonia.Controls + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Avalonia.DesignerSupport + + + {7062ae20-5dcc-4442-9645-8195bdece63e} + Avalonia.Diagnostics + + + {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0} + Avalonia.DotNetFrameworkRuntime + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Avalonia.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Avalonia.Layout + + + {b61b66a3-b82d-4875-8001-89d3394fe0c9} + Avalonia.Logging.Serilog + + + {6417b24e-49c2-4985-8db2-3ab9d898ec91} + Avalonia.ReactiveUI + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Avalonia.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Avalonia.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Avalonia.Themes.Default + + + {fb05ac90-89ba-4f2f-a924-f37875fb547c} + Avalonia.Cairo + + + {54f237d5-a70a-4752-9656-0c70b1a7b047} + Avalonia.Gtk + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Avalonia.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Avalonia.Markup + + + {3e908f67-5543-4879-a1dc-08eace79b3cd} + Avalonia.Direct2D1 + + + {811a76cf-1cf6-440f-963b-bbe31bd72a82} + Avalonia.Win32 + + + + + Designer + + + + + \ No newline at end of file diff --git a/samples/RenderTest/RenderTest.v2.ncrunchproject b/samples/RenderTest/RenderTest.v2.ncrunchproject new file mode 100644 index 0000000000..30815b1937 --- /dev/null +++ b/samples/RenderTest/RenderTest.v2.ncrunchproject @@ -0,0 +1,26 @@ + + 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/samples/RenderTest/packages.config b/samples/RenderTest/packages.config new file mode 100644 index 0000000000..3c79dde8c3 --- /dev/null +++ b/samples/RenderTest/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 673cd9b040..c118bbd9a9 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using Avalonia.Android.CanvasRendering; using Avalonia.Android.Platform; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.SkiaPlatform; @@ -10,6 +9,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; using Avalonia.Shared.PlatformSupport; +using Avalonia.Skia; namespace Avalonia { @@ -50,8 +50,9 @@ namespace Avalonia.Android .Bind().ToConstant(Instance) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() - .Bind().ToTransient() .Bind().ToConstant(Instance); + + SkiaPlatform.Initialize(); } public void Init(Type applicationType) diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 9af96f0f93..a412930b85 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -74,7 +74,6 @@ - diff --git a/src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs b/src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs deleted file mode 100644 index 0807471fe9..0000000000 --- a/src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Threading; -using System; -using System.Collections.Generic; - -namespace Avalonia.Android.CanvasRendering -{ - internal class AndroidTopLevelRenderer : ITopLevelRenderer - { - public void Attach(TopLevel topLevel) - { - var resources = new List(); - var initialClientSize = topLevel.PlatformImpl.ClientSize; - - - var queueManager = ((IRenderRoot)topLevel).RenderQueueManager; - - if (queueManager == null) - return; - - - var viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl); - resources.Add(viewport); - //resources.Add(queueManager.RenderNeeded.Subscribe(_ - // => - // Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize))))); - Action pendingInvalidation = null; - resources.Add(queueManager.RenderNeeded.Subscribe(_ => - { - if (pendingInvalidation == null) - { - pendingInvalidation = () => - { - topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize)); - pendingInvalidation = null; - }; - Dispatcher.UIThread.InvokeAsync(pendingInvalidation); - } - } - )); - - topLevel.PlatformImpl.Paint = rect => - { - viewport.Render(topLevel); - queueManager.RenderFinished(); - }; - - topLevel.Closed += delegate - { - foreach (var disposable in resources) - disposable.Dispose(); - resources.Clear(); - }; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs index a63ccec7c2..8101e4b550 100644 --- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs @@ -26,6 +26,5 @@ namespace Avalonia.Platform bool CurrentThreadIsLoopThread { get; } event Action Signaled; - } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 1991f49ac0..326556f629 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -2,10 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Reflection; using System.Threading; using Avalonia.Controls; -using Avalonia.Controls.Platform; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; @@ -177,7 +175,6 @@ namespace Avalonia .Bind().ToTransient() .Bind().ToConstant(_styler) .Bind().ToSingleton() - .Bind().ToTransient() .Bind().ToConstant(this); } } diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 3439ab3101..3d27a5e501 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -73,7 +73,6 @@ - diff --git a/src/Avalonia.Controls/Platform/ITopLevelRenderer.cs b/src/Avalonia.Controls/Platform/ITopLevelRenderer.cs deleted file mode 100644 index 1653ff0a6d..0000000000 --- a/src/Avalonia.Controls/Platform/ITopLevelRenderer.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Threading; - -namespace Avalonia.Controls.Platform -{ - public interface ITopLevelRenderer - { - void Attach(TopLevel topLevel); - } - - - class DefaultTopLevelRenderer : ITopLevelRenderer - { - - public void Attach(TopLevel topLevel) - { - var resources = new List(); - var initialClientSize = topLevel.PlatformImpl.ClientSize; - - - var queueManager = ((IRenderRoot)topLevel).RenderQueueManager; - - if (queueManager == null) - return; - - - var viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl); - resources.Add(viewport); - resources.Add(queueManager.RenderNeeded.Subscribe(_ - => - Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize))))); - - topLevel.PlatformImpl.Paint = rect => - { - try - { - viewport.Render(topLevel); - } - catch (RenderTargetCorruptedException ex) - { - Logging.Logger.Error("Renderer", this, "Render target was corrupted. Exception: {0}", ex); - viewport.Dispose(); - resources.Remove(viewport); - viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl); - resources.Add(viewport); - topLevel.PlatformImpl.Paint(rect); // Retry painting - } - queueManager.RenderFinished(); - }; - - topLevel.Closed += delegate - { - foreach (var disposable in resources) - disposable.Dispose(); - resources.Clear(); - }; - - } - } -} diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index 2125505efd..8f069a3ab2 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -12,13 +12,6 @@ namespace Avalonia.Controls.Platform static bool s_designerMode; - public static IRenderTarget CreateRenderTarget(ITopLevelImpl window) - { - return AvaloniaLocator.Current - .GetService() - .CreateRenderer(window.Handle); - } - public static IDisposable DesignerMode() { s_designerMode = true; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f64fb3cbd2..43c59f0b31 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -45,11 +45,11 @@ namespace Avalonia.Controls public static readonly StyledProperty PointerOverElementProperty = AvaloniaProperty.Register(nameof(IInputRoot.PointerOverElement)); - private readonly IRenderQueueManager _renderQueueManager; private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; private readonly IApplicationLifecycle _applicationLifecycle; + private readonly IPlatformRenderInterface _renderInterface; private Size _clientSize; private bool _isActive; @@ -86,22 +86,25 @@ namespace Avalonia.Controls } PlatformImpl = impl; - dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; var styler = TryGetService(dependencyResolver); + _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); - _renderQueueManager = TryGetService(dependencyResolver); _applicationLifecycle = TryGetService(dependencyResolver); + _renderInterface = TryGetService(dependencyResolver); - (dependencyResolver.GetService() ?? new DefaultTopLevelRenderer()).Attach(this); + var renderLoop = TryGetService(dependencyResolver); + var rendererFactory = TryGetService(dependencyResolver); + Renderer = rendererFactory?.CreateRenderer(this, renderLoop); PlatformImpl.SetInputRoot(this); PlatformImpl.Activated = HandleActivated; PlatformImpl.Deactivated = HandleDeactivated; PlatformImpl.Closed = HandleClosed; PlatformImpl.Input = HandleInput; + PlatformImpl.Paint = Renderer != null ? (Action)Renderer.Render : null; PlatformImpl.Resized = HandleResized; PlatformImpl.ScalingChanged = HandleScalingChanged; PlatformImpl.PositionChanged = HandlePositionChanged; @@ -179,9 +182,9 @@ namespace Avalonia.Controls } /// - /// Gets the window render manager. + /// Gets the renderer for the window. /// - IRenderQueueManager IRenderRoot.RenderQueueManager => _renderQueueManager; + public IRenderer Renderer { get; } /// /// Gets the access key handler for the window. @@ -231,6 +234,18 @@ namespace Avalonia.Controls private set; } + /// + IRenderTarget IRenderRoot.CreateRenderTarget() + { + return _renderInterface.CreateRenderTarget(PlatformImpl.Handle); + } + + /// + void IRenderRoot.Invalidate(Rect rect) + { + PlatformImpl.Invalidate(rect); + } + /// Point IRenderRoot.PointToClient(Point p) { @@ -321,8 +336,8 @@ namespace Avalonia.Controls } /// - /// Tries to get a service from an , throwing an - /// exception if not found. + /// Tries to get a service from an , logging a + /// warning if not found. /// /// The service type. /// The resolver. diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 9d3a5b6536..745de95bca 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls /// Subsequent ordering happens sequentially from top to bottom or from right to left, /// depending on the value of the Orientation property. /// - internal class WrapPanel : Panel, INavigableContainer + public class WrapPanel : Panel, INavigableContainer { /// /// Defines the property. diff --git a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj index f472c18230..3d2347683d 100644 --- a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj +++ b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj @@ -102,6 +102,12 @@ + + + + + + @@ -139,9 +145,6 @@ - - - diff --git a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs index 6beb787aab..c129cbd905 100644 --- a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -42,7 +42,7 @@ namespace Avalonia.Platform /// /// The platform handle for the renderer. /// An . - IRenderTarget CreateRenderer(IPlatformHandle handle); + IRenderTarget CreateRenderTarget(IPlatformHandle handle); /// /// Creates a render target bitmap implementation. diff --git a/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs b/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs new file mode 100644 index 0000000000..a4e8e6f0c2 --- /dev/null +++ b/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs @@ -0,0 +1,103 @@ +// 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 Avalonia.Platform; + +namespace Avalonia.Rendering +{ + /// + /// Defines a default render loop that uses a standard timer. + /// + /// + /// This class may be overridden by platform implementations to use a specialized timer + /// implementation. + /// + public class DefaultRenderLoop : IRenderLoop + { + private IPlatformThreadingInterface _threading; + private int _subscriberCount; + private EventHandler _tick; + private IDisposable _subscription; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of frames per second at which the loop should run. + /// + public DefaultRenderLoop(int framesPerSecond) + { + FramesPerSecond = framesPerSecond; + } + + /// + /// Gets the number of frames per second at which the loop runs. + /// + public int FramesPerSecond { get; } + + /// + public event EventHandler Tick + { + add + { + if (_subscriberCount++ == 0) + { + Start(); + } + + _tick += value; + } + + remove + { + if (--_subscriberCount == 0) + { + Stop(); + } + + _tick -= value; + } + } + + /// + /// Starts the timer. + /// + protected void Start() + { + _subscription = StartCore(InternalTick); + } + + /// + /// Provides the implementation of starting the timer. + /// + /// The method to call on each tick. + /// + /// This can be overridden by platform implementations to use a specialized timer + /// implementation. + /// + protected virtual IDisposable StartCore(Action tick) + { + if (_threading == null) + { + _threading = AvaloniaLocator.Current.GetService(); + } + + return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick); + } + + /// + /// Stops the timer. + /// + protected void Stop() + { + _subscription.Dispose(); + _subscription = null; + } + + private void InternalTick() + { + _tick(this, EventArgs.Empty); + } + } +} diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs b/src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs new file mode 100644 index 0000000000..36d915ddbd --- /dev/null +++ b/src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs @@ -0,0 +1,19 @@ +using System; + +namespace Avalonia.Rendering +{ + /// + /// Defines the interface implemented by an application render loop. + /// + public interface IRenderLoop + { + /// + /// Raised when the render loop ticks to signal a new frame should be drawn. + /// + /// + /// This event can be raised on any thread; it is the responsibility of the subscriber to + /// switch execution to the right thread. + /// + event EventHandler Tick; + } +} \ No newline at end of file diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs b/src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs deleted file mode 100644 index 580e8b3046..0000000000 --- a/src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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.Reactive; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - /// - /// Defines the interface for a . - /// - public interface IRenderQueueManager - { - /// - /// Gets an observable that is fired whenever a render is required. - /// - IObservable RenderNeeded { get; } - - /// - /// Invalidates the render for the specified visual and raises . - /// - /// The visual. - void InvalidateRender(IVisual visual); - - /// - /// Called when rendering is finished. - /// - void RenderFinished(); - } -} diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs b/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs index 254cb5bc1b..50cda1b1b8 100644 --- a/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs +++ b/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs @@ -1,19 +1,38 @@ // 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 Avalonia.Platform; +using Avalonia.VisualTree; namespace Avalonia.Rendering { /// /// Represents the root of a renderable tree. /// - public interface IRenderRoot + public interface IRenderRoot : IVisual { /// - /// Gets the render manager which schedules renders. + /// Gets the client size of the window. /// - IRenderQueueManager RenderQueueManager { get; } + Size ClientSize { get; } + + /// + /// Gets the renderer for the window. + /// + IRenderer Renderer { get; } + + /// + /// Creates a render target for the window. + /// + /// An . + IRenderTarget CreateRenderTarget(); + + /// + /// Adds a rectangle to the window's dirty region. + /// + /// The rectangle. + void Invalidate(Rect rect); /// /// Converts a point from screen to client coordinates. diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderer.cs b/src/Avalonia.SceneGraph/Rendering/IRenderer.cs new file mode 100644 index 0000000000..c643662179 --- /dev/null +++ b/src/Avalonia.SceneGraph/Rendering/IRenderer.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; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering +{ + public interface IRenderer : IDisposable + { + void AddDirty(IVisual visual); + + void Render(Rect rect); + } +} \ No newline at end of file diff --git a/src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs b/src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs new file mode 100644 index 0000000000..8e27f85193 --- /dev/null +++ b/src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs @@ -0,0 +1,18 @@ +using System; + +namespace Avalonia.Rendering +{ + /// + /// Defines a factory for creating instances. + /// + public interface IRendererFactory + { + /// + /// Creates a new renderer for the specified render root. + /// + /// The render root. + /// The render loop. + /// An instance of an . + IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop); + } +} diff --git a/src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs b/src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs deleted file mode 100644 index 8b64788c16..0000000000 --- a/src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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.Reactive; -using System.Reactive.Subjects; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - /// - /// Schedules the rendering of a tree. - /// - public class RenderQueueManager : IRenderQueueManager - { - private readonly Subject _renderNeeded = new Subject(); - - private bool _renderQueued; - - /// - /// Gets an observable that is fired whenever a render is required. - /// - public IObservable RenderNeeded => _renderNeeded; - - /// - /// Gets a value indicating whether a render is queued. - /// - public bool RenderQueued => _renderQueued; - - /// - /// Invalidates the render for the specified visual and raises . - /// - /// The visual. - public void InvalidateRender(IVisual visual) - { - if (!_renderQueued) - { - _renderQueued = true; - _renderNeeded.OnNext(Unit.Default); - } - } - - /// - /// Called when rendering is finished. - /// - public void RenderFinished() - { - _renderQueued = false; - } - } -} diff --git a/src/Avalonia.SceneGraph/Rendering/Renderer.cs b/src/Avalonia.SceneGraph/Rendering/Renderer.cs new file mode 100644 index 0000000000..ed58d129af --- /dev/null +++ b/src/Avalonia.SceneGraph/Rendering/Renderer.cs @@ -0,0 +1,67 @@ +// 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 Avalonia.Platform; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering +{ + public class Renderer : IDisposable, IRenderer + { + private readonly IRenderLoop _renderLoop; + private readonly IRenderRoot _root; + private IRenderTarget _renderTarget; + private bool _dirty; + + public Renderer(IRenderRoot root, IRenderLoop renderLoop) + { + Contract.Requires(root != null); + + _root = root; + _renderLoop = renderLoop; + _renderLoop.Tick += OnRenderLoopTick; + } + + public void AddDirty(IVisual visual) + { + _dirty = true; + } + + public void Dispose() + { + _renderLoop.Tick -= OnRenderLoopTick; + } + + public void Render(Rect rect) + { + if (_renderTarget == null) + { + _renderTarget = _root.CreateRenderTarget(); + } + + try + { + _renderTarget.Render(_root); + } + catch (RenderTargetCorruptedException ex) + { + Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + _renderTarget.Dispose(); + _renderTarget = null; + } + finally + { + _dirty = false; + } + } + + private void OnRenderLoopTick(object sender, EventArgs e) + { + if (_dirty) + { + _root.Invalidate(new Rect(_root.ClientSize)); + } + } + } +} diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs index f5dc3c4566..3d13501edd 100644 --- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs @@ -1,4 +1,4 @@ -// Copyright (c) The Avalonia Project. All rights reserved. +// 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; @@ -51,7 +51,7 @@ namespace Avalonia.Rendering s_currentFrames++; var now = s_stopwatch.Elapsed; var elapsed = now - s_lastMeasure; - if (elapsed.TotalSeconds > 0) + if (elapsed.TotalSeconds > 1) { s_fps = (int) (s_currentFrames/elapsed.TotalSeconds); s_currentFrames = 0; diff --git a/src/Avalonia.SceneGraph/Visual.cs b/src/Avalonia.SceneGraph/Visual.cs index 54786ac85a..dbc678eee5 100644 --- a/src/Avalonia.SceneGraph/Visual.cs +++ b/src/Avalonia.SceneGraph/Visual.cs @@ -259,7 +259,7 @@ namespace Avalonia /// public void InvalidateVisual() { - VisualRoot?.RenderQueueManager?.InvalidateRender(this); + VisualRoot?.Renderer?.AddDirty(this); } /// diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index 1e6eef103b..e59bcfffa1 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -50,7 +50,7 @@ namespace Avalonia.Cairo return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } - public IRenderTarget CreateRenderer(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IPlatformHandle handle) { var window = handle as Gtk.Window; if (window != null) diff --git a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs index e79c51915d..38bada2f25 100644 --- a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs +++ b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs @@ -25,9 +25,10 @@ namespace Avalonia namespace Avalonia.Gtk { using System.IO; + using Rendering; using Gtk = global::Gtk; - public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader + public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IRendererFactory { private static readonly GtkPlatform s_instance = new GtkPlatform(); private static Thread _uiThread; @@ -53,6 +54,8 @@ namespace Avalonia.Gtk .Bind().ToConstant(GtkMouseDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) + .Bind().ToConstant(s_instance) + .Bind().ToConstant(new DefaultRenderLoop(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance); _uiThread = Thread.CurrentThread; @@ -110,6 +113,11 @@ namespace Avalonia.Gtk return new PopupImpl(); } + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) + { + return new Renderer(root, renderLoop); + } + public IWindowIconImpl LoadIcon(string fileName) { return new IconImpl(new Gdk.Pixbuf(fileName)); diff --git a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs index 5282a4a3e0..62d8e7bd18 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs @@ -22,7 +22,7 @@ namespace Avalonia.Skia.Android { _renderTarget = AvaloniaLocator.Current.GetService() - .CreateRenderer(this); + .CreateRenderTarget(this); } protected override void Draw() diff --git a/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs b/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs index 6faba3d8dc..c27f54b277 100644 --- a/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs +++ b/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs @@ -21,7 +21,7 @@ namespace Avalonia.Skia.iOS.TestApp AutoresizingMask = UIViewAutoresizing.All; SkiaPlatform.Initialize(); _target = AvaloniaLocator.Current.GetService() - .CreateRenderer(AvaloniaPlatformHandle); + .CreateRenderTarget(AvaloniaPlatformHandle); UpdateText(0); } double _radians = 0; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 86f884b592..f37fd7c1c6 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -58,7 +58,7 @@ namespace Avalonia.Skia return new BitmapImpl(width, height); } - public IRenderTarget CreateRenderer(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IPlatformHandle handle) { return new WindowRenderTarget(handle); } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 7222976a33..b43eef2fa9 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -7,6 +7,7 @@ using Avalonia.Direct2D1.Media; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; +using Avalonia.Rendering; namespace Avalonia { @@ -22,7 +23,7 @@ namespace Avalonia namespace Avalonia.Direct2D1 { - public class Direct2D1Platform : IPlatformRenderInterface + public class Direct2D1Platform : IPlatformRenderInterface, IRendererFactory { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); @@ -38,6 +39,7 @@ namespace Avalonia.Direct2D1 public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) + .Bind().ToConstant(s_instance) .BindToSelf(s_d2D1Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory); @@ -59,7 +61,12 @@ namespace Avalonia.Direct2D1 return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, wrapping); } - public IRenderTarget CreateRenderer(IPlatformHandle handle) + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) + { + return new Renderer(root, renderLoop); + } + + public IRenderTarget CreateRenderTarget(IPlatformHandle handle) { if (handle.HandleDescriptor == "HWND") { diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index c4bb5c8a25..51ad5ce05e 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ae030df49a..d115b35a23 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -23,6 +23,8 @@ namespace Avalonia.Win32.Interop public delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime); + public delegate void TimeCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2); + public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); public enum Cursor @@ -705,8 +707,6 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); - - [DllImport("user32.dll")] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); @@ -738,6 +738,12 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow); + [DllImport("Winmm.dll")] + public static extern uint timeKillEvent(uint uTimerID); + + [DllImport("Winmm.dll")] + public static extern uint timeSetEvent(uint uDelay, uint uResolution, TimeCallback lpTimeProc, UIntPtr dwUser, uint fuEvent); + [DllImport("user32.dll")] public static extern int ToUnicode( uint virtualKeyCode, diff --git a/src/Windows/Avalonia.Win32/RenderLoop.cs b/src/Windows/Avalonia.Win32/RenderLoop.cs new file mode 100644 index 0000000000..7d7befcc33 --- /dev/null +++ b/src/Windows/Avalonia.Win32/RenderLoop.cs @@ -0,0 +1,35 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.Rendering; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + internal class RenderLoop : DefaultRenderLoop + { + private UnmanagedMethods.TimeCallback timerDelegate; + + public RenderLoop(int framesPerSecond) + : base(framesPerSecond) + { + } + + protected override IDisposable StartCore(Action tick) + { + timerDelegate = (id, uMsg, user, dw1, dw2) => tick(); + + var handle = UnmanagedMethods.timeSetEvent( + (uint)(1000 / FramesPerSecond), + 0, + timerDelegate, + UIntPtr.Zero, + 1); + + return Disposable.Create(() => + { + timerDelegate = null; + UnmanagedMethods.timeKillEvent(handle); + }); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 4aa1346a98..b437100cee 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -16,6 +16,7 @@ using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using Avalonia.Controls; using System.IO; +using Avalonia.Rendering; namespace Avalonia { @@ -65,6 +66,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(WindowsMouseDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) + .Bind().ToConstant(new RenderLoop(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 872a7cf36b..65e10cafda 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -537,18 +537,15 @@ namespace Avalonia.Win32 break; case UnmanagedMethods.WindowsMessage.WM_PAINT: - if (Paint != null) - { - UnmanagedMethods.PAINTSTRUCT ps; + UnmanagedMethods.PAINTSTRUCT ps; - if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) - { - UnmanagedMethods.RECT r; - UnmanagedMethods.GetUpdateRect(_hwnd, out r, false); - var f = Scaling; - Paint(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); - UnmanagedMethods.EndPaint(_hwnd, ref ps); - } + if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) + { + UnmanagedMethods.RECT r; + UnmanagedMethods.GetUpdateRect(_hwnd, out r, false); + var f = Scaling; + Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); + UnmanagedMethods.EndPaint(_hwnd, ref ps); } return IntPtr.Zero; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 1433bc7f85..847662e629 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; +using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.UnitTests; using Xunit; @@ -315,7 +316,18 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestScroller : ScrollContentPresenter, IRenderRoot { - public IRenderQueueManager RenderQueueManager { get; } + public IRenderer Renderer { get; } + public Size ClientSize { get; } + + public IRenderTarget CreateRenderTarget() + { + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); + } public Point PointToClient(Point point) { diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 8d10b01f94..e603925e31 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -12,6 +12,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.UnitTests; using Xunit; @@ -990,7 +991,18 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestScroller : ScrollContentPresenter, IRenderRoot { - public IRenderQueueManager RenderQueueManager { get; } + public IRenderer Renderer { get; } + public Size ClientSize { get; } + + public IRenderTarget CreateRenderTarget() + { + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); + } public Point PointToClient(Point point) { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 54338a773e..367070b37a 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -268,7 +268,6 @@ namespace Avalonia.Controls.UnitTests.Primitives globalStyles.Setup(x => x.Styles).Returns(styles); var renderInterface = new Mock(); - renderInterface.Setup(x => x.CreateRenderer(It.IsAny())).Returns(() => new Mock().Object); AvaloniaLocator.CurrentMutable .Bind().ToTransient() diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs index f4cca0c684..e4e7551f5c 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs @@ -340,7 +340,7 @@ namespace Avalonia.Input.UnitTests throw new NotImplementedException(); } - public IRenderTarget CreateRenderer(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IPlatformHandle handle) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index b1f39673c4..b3e9b981a5 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -137,7 +137,6 @@ namespace Avalonia.Layout.UnitTests var formattedText = fixture.Create(); var globalStyles = new Mock(); var renderInterface = fixture.Create(); - var renderManager = fixture.Create(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.ClientSize); @@ -151,7 +150,6 @@ namespace Avalonia.Layout.UnitTests .Bind().ToConstant(new LayoutManager()) .Bind().ToConstant(new AppBuilder().RuntimePlatform) .Bind().ToConstant(renderInterface) - .Bind().ToConstant(renderManager) .Bind().ToConstant(new Styler()) .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 99a7bbbcd2..1e257a698c 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -11,9 +11,11 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Diagnostics; using Avalonia.Layout; +using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; using Xunit.Abstractions; @@ -52,6 +54,7 @@ namespace Avalonia.LeakTests }; var result = run(); + PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -87,6 +90,7 @@ namespace Avalonia.LeakTests }; var result = run(); + PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -123,6 +127,7 @@ namespace Avalonia.LeakTests }; var result = run(); + PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -158,6 +163,7 @@ namespace Avalonia.LeakTests }; var result = run(); + PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -201,6 +207,7 @@ namespace Avalonia.LeakTests }; var result = run(); + PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -287,25 +294,19 @@ namespace Avalonia.LeakTests }; var result = run(); + PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } } - private class TestTemplatedControl : TemplatedControl + private static void PurgeMoqReferences() { - public static readonly StyledProperty IsCanvasVisibleProperty = - AvaloniaProperty.Register("IsCanvasVisible"); - - public TestTemplatedControl() - { - Template = new FuncControlTemplate(parent => - new Canvas - { - [~IsVisibleProperty] = parent[~IsCanvasVisibleProperty] - }); - } + // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called; + // clear these. + var renderer = Mock.Get(AvaloniaLocator.Current.GetService()); + renderer.ResetCalls(); } private class Node diff --git a/tests/Avalonia.SceneGraph.UnitTests/TestRoot.cs b/tests/Avalonia.SceneGraph.UnitTests/TestRoot.cs index 7ac49c4b89..aa5eb64691 100644 --- a/tests/Avalonia.SceneGraph.UnitTests/TestRoot.cs +++ b/tests/Avalonia.SceneGraph.UnitTests/TestRoot.cs @@ -9,12 +9,19 @@ namespace Avalonia.SceneGraph.UnitTests { public class TestRoot : TestVisual, IRenderRoot { - public IRenderTarget RenderTarget + public Size ClientSize { get; } + + public IRenderTarget CreateRenderTarget() { - get { throw new NotImplementedException(); } + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); } - public IRenderQueueManager RenderQueueManager + public IRenderer Renderer { get { throw new NotImplementedException(); } } diff --git a/tests/Avalonia.SceneGraph.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.SceneGraph.UnitTests/VisualTree/MockRenderInterface.cs index 226a452007..d7c8899f8a 100644 --- a/tests/Avalonia.SceneGraph.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.SceneGraph.UnitTests/VisualTree/MockRenderInterface.cs @@ -20,7 +20,7 @@ namespace Avalonia.SceneGraph.UnitTests.VisualTree throw new NotImplementedException(); } - public IRenderTarget CreateRenderer(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IPlatformHandle handle) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 769d00b3b6..064d5d36d3 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -41,7 +41,17 @@ namespace Avalonia.UnitTests public IRenderTarget RenderTarget => null; - public IRenderQueueManager RenderQueueManager => null; + public IRenderer Renderer => null; + + public IRenderTarget CreateRenderTarget() + { + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); + } public Point PointToClient(Point p) => p; diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index d322a6dbbc..8dc838163f 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -11,6 +11,7 @@ using Avalonia.Platform; using Avalonia.Shared.PlatformSupport; using Avalonia.Styling; using Avalonia.Themes.Default; +using Avalonia.Rendering; namespace Avalonia.UnitTests { @@ -20,7 +21,9 @@ namespace Avalonia.UnitTests assetLoader: new AssetLoader(), layoutManager: new LayoutManager(), platform: new AppBuilder().RuntimePlatform, + renderer: Mock.Of(), renderInterface: CreateRenderInterfaceMock(), + renderLoop: Mock.Of(), standardCursorFactory: Mock.Of(), styler: new Styler(), theme: () => CreateDefaultTheme(), @@ -57,7 +60,9 @@ namespace Avalonia.UnitTests Func keyboardDevice = null, ILayoutManager layoutManager = null, IRuntimePlatform platform = null, + IRenderer renderer = null, IPlatformRenderInterface renderInterface = null, + IRenderLoop renderLoop = null, IStandardCursorFactory standardCursorFactory = null, IStyler styler = null, Func theme = null, @@ -71,7 +76,9 @@ namespace Avalonia.UnitTests KeyboardDevice = keyboardDevice; LayoutManager = layoutManager; Platform = platform; + Renderer = renderer; RenderInterface = renderInterface; + RenderLoop = renderLoop; StandardCursorFactory = standardCursorFactory; Styler = styler; Theme = theme; @@ -86,7 +93,9 @@ namespace Avalonia.UnitTests public Func KeyboardDevice { get; } public ILayoutManager LayoutManager { get; } public IRuntimePlatform Platform { get; } + public IRenderer Renderer { get; } public IPlatformRenderInterface RenderInterface { get; } + public IRenderLoop RenderLoop { get; } public IStandardCursorFactory StandardCursorFactory { get; } public IStyler Styler { get; } public Func Theme { get; } @@ -101,7 +110,9 @@ namespace Avalonia.UnitTests Func keyboardDevice = null, ILayoutManager layoutManager = null, IRuntimePlatform platform = null, + IRenderer renderer = null, IPlatformRenderInterface renderInterface = null, + IRenderLoop renderLoop = null, IStandardCursorFactory standardCursorFactory = null, IStyler styler = null, Func theme = null, @@ -116,7 +127,9 @@ namespace Avalonia.UnitTests keyboardDevice: keyboardDevice ?? KeyboardDevice, layoutManager: layoutManager ?? LayoutManager, platform: platform ?? Platform, + renderer: renderer ?? Renderer, renderInterface: renderInterface ?? RenderInterface, + renderLoop: renderLoop ?? RenderLoop, standardCursorFactory: standardCursorFactory ?? StandardCursorFactory, styler: styler ?? Styler, theme: theme ?? Theme, @@ -152,8 +165,7 @@ namespace Avalonia.UnitTests It.IsAny(), It.IsAny()) == Mock.Of() && x.CreateStreamGeometry() == Mock.Of( - y => y.Open() == Mock.Of()) && - x.CreateRenderer(It.IsAny()) == Mock.Of()); + y => y.Open() == Mock.Of())); } } } diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs index 35ff32d721..73c9f370d6 100644 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs @@ -43,7 +43,17 @@ namespace Avalonia.UnitTests public IRenderTarget RenderTarget => null; - public IRenderQueueManager RenderQueueManager => null; + public IRenderer Renderer => null; + + public IRenderTarget CreateRenderTarget() + { + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); + } public Point PointToClient(Point p) => p; diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index d76139c809..0dec218432 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -7,6 +7,7 @@ using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Styling; using Avalonia.Controls; +using Avalonia.Rendering; namespace Avalonia.UnitTests { @@ -42,7 +43,9 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.KeyboardDevice?.Invoke()) .Bind().ToConstant(Services.LayoutManager) .Bind().ToConstant(Services.Platform) + .Bind().ToConstant(Services.Renderer) .Bind().ToConstant(Services.RenderInterface) + .Bind().ToConstant(Services.RenderLoop) .Bind().ToConstant(Services.ThreadingInterface) .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler)