From 7de42e02e1c5d6af042c3b4b97e842e4f9cec159 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 19 Aug 2017 17:11:07 +0200 Subject: [PATCH 01/16] Clear scene for invisible root visuals. When a root visual is hidden, clear `DeferredRenderer._scene` and completely rebuild it if it is shown again. Fixes #1096 --- .../Rendering/DeferredRenderer.cs | 42 +++++++++++-------- .../Rendering/SceneGraph/SceneBuilder.cs | 6 +++ .../SceneGraph/SceneBuilderTests_Layers.cs | 27 ------------ 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index d3d6776efa..c30fb3bdc3 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.IO; using Avalonia.Media.Immutable; using System.Threading; +using System.Linq; namespace Avalonia.Rendering { @@ -58,7 +59,6 @@ namespace Avalonia.Rendering _dispatcher = dispatcher ?? Dispatcher.UIThread; _root = root; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - _scene = new Scene(root); _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); _layers = new RenderLayers(_layerFactory); _renderLoop = renderLoop; @@ -86,7 +86,6 @@ namespace Avalonia.Rendering _root = root; _renderTarget = renderTarget; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - _scene = new Scene(root); _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); _layers = new RenderLayers(_layerFactory); } @@ -122,7 +121,7 @@ namespace Avalonia.Rendering UpdateScene(); } - return _scene.HitTest(p, filter); + return _scene?.HitTest(p, filter) ?? Enumerable.Empty(); } /// @@ -186,7 +185,7 @@ namespace Avalonia.Rendering _dirtyRectsDisplay.Tick(); } - if (scene.Size != Size.Empty) + if (scene != null && scene.Size != Size.Empty) { if (scene.Generation != _lastSceneId) { @@ -366,25 +365,32 @@ namespace Avalonia.Rendering try { - var scene = _scene.Clone(); - - if (_dirty == null) - { - _dirty = new DirtyVisuals(); - _sceneBuilder.UpdateAll(scene); - } - else if (_dirty.Count > 0) + if (_root.IsVisible) { - foreach (var visual in _dirty) + var scene = _scene?.Clone() ?? new Scene(_root); + + if (_dirty == null) { - _sceneBuilder.Update(scene, visual); + _dirty = new DirtyVisuals(); + _sceneBuilder.UpdateAll(scene); + } + else if (_dirty.Count > 0) + { + foreach (var visual in _dirty) + { + _sceneBuilder.Update(scene, visual); + } } - } - Interlocked.Exchange(ref _scene, scene); + Interlocked.Exchange(ref _scene, scene); - _dirty.Clear(); - (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); + _dirty.Clear(); + (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); + } + else + { + Interlocked.Exchange(ref _scene, null); + } } finally { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index afdf488b31..10455eb147 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -36,8 +36,14 @@ namespace Avalonia.Rendering.SceneGraph { Contract.Requires(scene != null); Contract.Requires(visual != null); + Dispatcher.UIThread.VerifyAccess(); + if (!scene.Root.Visual.IsVisible) + { + throw new AvaloniaInternalException("Cannot update the scene for an invisible root visual."); + } + var node = (VisualNode)scene.FindNode(visual); if (visual == scene.Root.Visual) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs index 13f28018db..e65487ac44 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -273,32 +273,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph ((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); - } - } } } From b83d96e1cb2bfe951f5513270de0675cfec0a4e6 Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 20 Aug 2017 11:55:55 +0200 Subject: [PATCH 02/16] Have logging project use SharedAssemblyInfo --- .../Avalonia.Logging.Serilog.csproj | 3 +++ .../Properties/AssemblyInfo.cs | 27 ------------------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj b/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj index 508ede7f7d..9ac40cba07 100644 --- a/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj +++ b/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj @@ -23,6 +23,9 @@ bin\Release\Avalonia.Logging.Serilog.XML true + + + diff --git a/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs b/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs index 64c0eaffae..35b2e48f70 100644 --- a/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs @@ -1,30 +1,3 @@ -using System.Resources; 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("Avalonia.Serilog")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.Serilog")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// 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")] From a934f276ff540a9816e082ce677b96a37ed3ae9f Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 20 Aug 2017 11:58:31 +0200 Subject: [PATCH 03/16] Have Avalonia.Cairo use SharedAssemblyInfo --- src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj | 3 +++ .../Avalonia.Cairo/Properties/AssemblyInfo.cs | 20 ------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj b/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj index d09fd2ddc6..460db98895 100644 --- a/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj +++ b/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj @@ -47,6 +47,9 @@ + + Properties\SharedAssemblyInfo.cs + diff --git a/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs index dc7d815ddc..709f1eb22a 100644 --- a/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs @@ -11,13 +11,6 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Avalonia.Cairo")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.Cairo")] -[assembly: AssemblyCopyright("Copyright \u00A9 2014")] -[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 @@ -27,19 +20,6 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("f999ba8b-64e7-40cc-98a4-003f1852d2a3")] -// 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")] - [assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 3, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] [assembly: ExportRenderingSubsystem(OperatingSystemType.Linux, 2, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] [assembly: ExportRenderingSubsystem(OperatingSystemType.OSX, 3, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] From 29352969a3b590f5b3583fb13b7d45cb3817c271 Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 20 Aug 2017 12:02:11 +0200 Subject: [PATCH 04/16] Have Avalonia.Gtk use SharedAssemblyInfo --- src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj | 3 +++ src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs | 13 ------------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index b51377f29c..6049869424 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -39,6 +39,9 @@ + + Properties\SharedAssemblyInfo.cs + diff --git a/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs index 6d1eb24836..67e5f6dc17 100644 --- a/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs @@ -4,23 +4,10 @@ using Avalonia.Gtk; using Avalonia.Platform; using System.Reflection; -using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. [assembly: AssemblyTitle("Avalonia.Gtk")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("steven")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("1.0.*")] [assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 3, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] [assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 2, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] From 5d945a9dcddcd38cad1478632f9057afff80cfd8 Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 20 Aug 2017 12:04:06 +0200 Subject: [PATCH 05/16] Have Avalonia.Gtk3 use SharedAssemblyInfo --- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 1 + .../Avalonia.Gtk3/Properties/AssemblyInfo.cs | 25 +------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index d292e99b30..0f4b8c2e1b 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -24,6 +24,7 @@ true + KeyTransform.cs diff --git a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs index 6b040240c4..034b73f699 100644 --- a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs @@ -1,7 +1,4 @@ -using System.Resources; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Reflection; using Avalonia.Gtk3; using Avalonia.Platform; @@ -9,27 +6,7 @@ using Avalonia.Platform; // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Avalonia.Gtk3")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.Gtk3")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] -// 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")] [assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] [assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 1, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] [assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] \ No newline at end of file From 261cdc1e5f2abbf6dad316d761f6aefed4869d58 Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 20 Aug 2017 12:05:14 +0200 Subject: [PATCH 06/16] Have Avalonia.DotNetFrameworkRuntime use SharedAssemblyInfo --- .../Avalonia.DotNetFrameworkRuntime.csproj | 3 +++ .../Properties/AssemblyInfo.cs | 21 ------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index 2fbcba40c8..e2c866fe3d 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -44,6 +44,9 @@ + + Properties\SharedAssemblyInfo.cs + diff --git a/src/Avalonia.DotNetFrameworkRuntime/Properties/AssemblyInfo.cs b/src/Avalonia.DotNetFrameworkRuntime/Properties/AssemblyInfo.cs index f55d3056f6..3a91d50a24 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Properties/AssemblyInfo.cs +++ b/src/Avalonia.DotNetFrameworkRuntime/Properties/AssemblyInfo.cs @@ -1,18 +1,10 @@ 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("Avalonia.DotNetFrameworkRuntime")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.DotNetFrameworkRuntime")] -[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 @@ -21,16 +13,3 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("4a1abb09-9047-4bd5-a4ad-a055e52c5ee0")] - -// 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")] From 1d8ade7d9348a550435f7ab5ce6cf41db25891cd Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 20 Aug 2017 12:12:11 +0200 Subject: [PATCH 07/16] Have Avalonia.ReactUI use SharedAssemblyInfo --- .../Avalonia.ReactiveUI.csproj | 1 + .../Properties/AssemblyInfo.cs | 24 ------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 2d66b62eab..22d815d786 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs index 358c6224fb..c8a5c5cc41 100644 --- a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs +++ b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs @@ -1,33 +1,9 @@ // 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.Resources; 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("Avalonia.ReactiveUI")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.ReactiveUI")] -[assembly: AssemblyCopyright("Copyright \u00A9 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// 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")] From 8cc6d3069638811929132d6ce583503c3d1706b8 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 14:05:13 +0300 Subject: [PATCH 08/16] Matrix.Parse & type converter --- src/Avalonia.Visuals/Matrix.cs | 29 +++++++++++++++++++ src/Avalonia.Visuals/Point.cs | 2 +- .../Avalonia.Markup.Xaml.csproj | 1 + .../Converters/MatrixTypeConverter.cs | 23 +++++++++++++++ .../AvaloniaDefaultTypeConverters.cs | 1 + .../Media/MatrixTests.cs | 16 ++++++++++ 6 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 8812000bdc..ee31ea9152 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using System.Linq; namespace Avalonia { @@ -295,5 +296,33 @@ namespace Avalonia ((_m21 * _m32) - (_m22 * _m31)) / d, ((_m12 * _m31) - (_m11 * _m32)) / d); } + + /// + /// Parses a string. + /// + /// The string. + /// The current culture. + /// The . + public static Matrix Parse(string s, CultureInfo culture) + { + var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()) + .ToArray(); + + if (parts.Length == 6) + { + return new Matrix( + double.Parse(parts[0], culture), + double.Parse(parts[1], culture), + double.Parse(parts[2], culture), + double.Parse(parts[3], culture), + double.Parse(parts[4], culture), + double.Parse(parts[5], culture)); + } + else + { + throw new FormatException("Invalid Matrix."); + } + } } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 7c7a3336fc..5fbd082967 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -183,7 +183,7 @@ namespace Avalonia } else { - throw new FormatException("Invalid Thickness."); + throw new FormatException("Invalid Point."); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index dbf985fd79..84c0f1ef1c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs new file mode 100644 index 0000000000..c477ff5637 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs @@ -0,0 +1,23 @@ +// 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.Globalization; + +namespace Avalonia.Markup.Xaml.Converters +{ + using System.ComponentModel; + + public class MatrixTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return Matrix.Parse((string)value, culture); + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs index 66e9a697e4..f8b99f2fdf 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs @@ -32,6 +32,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml { typeof(AvaloniaList), typeof(AvaloniaListTypeConverter) }, { typeof(IMemberSelector), typeof(MemberSelectorTypeConverter) }, { typeof(Point), typeof(PointTypeConverter) }, + { typeof(Matrix), typeof(MatrixTypeConverter) }, { typeof(IList), typeof(PointsListTypeConverter) }, { typeof(AvaloniaProperty), typeof(AvaloniaPropertyTypeConverter) }, { typeof(RelativePoint), typeof(RelativePointTypeConverter) }, diff --git a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs new file mode 100644 index 0000000000..4c1e361952 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs @@ -0,0 +1,16 @@ +using System.Globalization; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class MatrixTests + { + [Fact] + public void Parse_Parses() + { + var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture); + var expected = new Matrix(1, 2, 3, -4, 5, 6); + Assert.Equal(expected, matrix); + } + } +} \ No newline at end of file From f05cb39a36bdd8226005a652c97db5c31838894c Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 15:00:18 +0300 Subject: [PATCH 09/16] Allow Geometry classes to be instantiated in XAML --- src/Avalonia.Visuals/Media/EllipseGeometry.cs | 51 ++++++-- src/Avalonia.Visuals/Media/LineGeometry.cs | 80 ++++++++++-- .../Media/PolylineGeometry.cs | 119 +++++++++++++++--- .../Media/RectangleGeometry.cs | 51 ++++++-- src/Avalonia.Visuals/Points.cs | 9 ++ 5 files changed, 256 insertions(+), 54 deletions(-) create mode 100644 src/Avalonia.Visuals/Points.cs diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs index 414fd9ab59..591b55cf58 100644 --- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs +++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs @@ -11,16 +11,51 @@ namespace Avalonia.Media /// public class EllipseGeometry : Geometry { + /// + /// Defines the property. + /// + public static readonly StyledProperty RectProperty = + AvaloniaProperty.Register(nameof(Rect)); + + public Rect Rect + { + get => GetValue(RectProperty); + set => SetValue(RectProperty, value); + } + + static EllipseGeometry() + { + RectProperty.Changed.AddClassHandler(x => x.RectChanged); + } + /// /// Initializes a new instance of the class. /// - /// The rectangle that the ellipse should fill. - public EllipseGeometry(Rect rect) + public EllipseGeometry() { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + PlatformImpl = factory.CreateStreamGeometry(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The rectangle that the ellipse should fill. + public EllipseGeometry(Rect rect) : this() + { + Rect = rect; + } + + /// + public override Geometry Clone() + { + return new EllipseGeometry(Rect); + } - using (IStreamGeometryContextImpl ctx = impl.Open()) + private void RectChanged(AvaloniaPropertyChangedEventArgs e) + { + var rect = (Rect)e.NewValue; + using (var ctx = ((IStreamGeometryImpl)PlatformImpl).Open()) { double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3; var center = rect.Center; @@ -45,14 +80,6 @@ namespace Avalonia.Media ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0)); ctx.EndFigure(true); } - - PlatformImpl = impl; - } - - /// - public override Geometry Clone() - { - return new EllipseGeometry(Bounds); } } } diff --git a/src/Avalonia.Visuals/Media/LineGeometry.cs b/src/Avalonia.Visuals/Media/LineGeometry.cs index 2783d7fb26..323bfa5a7e 100644 --- a/src/Avalonia.Visuals/Media/LineGeometry.cs +++ b/src/Avalonia.Visuals/Media/LineGeometry.cs @@ -10,35 +10,89 @@ namespace Avalonia.Media /// public class LineGeometry : Geometry { - private Point _startPoint; - private Point _endPoint; + /// + /// Defines the property. + /// + public static readonly StyledProperty StartPointProperty = + AvaloniaProperty.Register(nameof(StartPoint)); + + public Point StartPoint + { + get => GetValue(StartPointProperty); + set => SetValue(StartPointProperty, value); + } + + /// + /// Defines the property. + /// + public static readonly StyledProperty EndPointProperty = + AvaloniaProperty.Register(nameof(EndPoint)); + private bool _isDirty; + + public Point EndPoint + { + get => GetValue(EndPointProperty); + set => SetValue(EndPointProperty, value); + } + + static LineGeometry() + { + StartPointProperty.Changed.AddClassHandler(x => x.PointsChanged); + EndPointProperty.Changed.AddClassHandler(x => x.PointsChanged); + } + + /// + /// Initializes a new instance of the class. + /// + public LineGeometry() + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + PlatformImpl = factory.CreateStreamGeometry(); + } /// /// Initializes a new instance of the class. /// /// The start point. /// The end point. - public LineGeometry(Point startPoint, Point endPoint) + public LineGeometry(Point startPoint, Point endPoint) : this() { - _startPoint = startPoint; - _endPoint = endPoint; - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + StartPoint = startPoint; + EndPoint = endPoint; + } - using (IStreamGeometryContextImpl context = impl.Open()) + public override IGeometryImpl PlatformImpl + { + get { - context.BeginFigure(_startPoint, false); - context.LineTo(_endPoint); - context.EndFigure(false); + PrepareIfNeeded(); + return base.PlatformImpl; } + protected set => base.PlatformImpl = value; + } - PlatformImpl = impl; + public void PrepareIfNeeded() + { + if (_isDirty) + { + _isDirty = false; + + using (var context = ((IStreamGeometryImpl)PlatformImpl).Open()) + { + context.BeginFigure(StartPoint, false); + context.LineTo(EndPoint); + context.EndFigure(false); + } + } } /// public override Geometry Clone() { - return new LineGeometry(_startPoint, _endPoint); + PrepareIfNeeded(); + return new LineGeometry(StartPoint, EndPoint); } + + private void PointsChanged(AvaloniaPropertyChangedEventArgs e) => _isDirty = true; } } diff --git a/src/Avalonia.Visuals/Media/PolylineGeometry.cs b/src/Avalonia.Visuals/Media/PolylineGeometry.cs index 709ad7a9f5..c8d7ac163c 100644 --- a/src/Avalonia.Visuals/Media/PolylineGeometry.cs +++ b/src/Avalonia.Visuals/Media/PolylineGeometry.cs @@ -3,10 +3,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Platform; +using Avalonia.Metadata; +using Avalonia.Collections; namespace Avalonia.Media { @@ -15,36 +14,122 @@ namespace Avalonia.Media /// public class PolylineGeometry : Geometry { - private IList _points; - private bool _isFilled; + /// + /// Defines the property. + /// + public static readonly DirectProperty PointsProperty = + AvaloniaProperty.RegisterDirect(nameof(Points), g => g.Points, (g, f) => g.Points = f); - public PolylineGeometry(IList points, bool isFilled) + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty IsFilledProperty = + AvaloniaProperty.Register(nameof(IsFilled)); + + static PolylineGeometry() + { + PointsProperty.Changed.Subscribe(onNext: v => + { + (v.Sender as PolylineGeometry)?.OnPointsChanged(v.OldValue as Points, v.NewValue as Points); + }); + IsFilledProperty.Changed.AddClassHandler(x => a => x.NotifyChanged()); + } + + /// + /// Initializes a new instance of the class. + /// + public PolylineGeometry() { - _points = points; - _isFilled = isFilled; IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + PlatformImpl = factory.CreateStreamGeometry(); + + Points = new Points(); + } + + /// + /// Initializes a new instance of the class. + /// + public PolylineGeometry(IEnumerable points, bool isFilled) : this() + { + Points.AddRange(points); + IsFilled = isFilled; + + PrepareIfNeeded(); + } - using (IStreamGeometryContextImpl context = impl.Open()) + public void PrepareIfNeeded() + { + if (_isDirty) { - if (points.Count > 0) + _isDirty = false; + + using (var context = ((IStreamGeometryImpl)PlatformImpl).Open()) { - context.BeginFigure(points[0], isFilled); - for (int i = 1; i < points.Count; i++) + var points = Points; + var isFilled = IsFilled; + if (points.Count > 0) { - context.LineTo(points[i]); + context.BeginFigure(points[0], isFilled); + for (int i = 1; i < points.Count; i++) + { + context.LineTo(points[i]); + } + context.EndFigure(isFilled); } - context.EndFigure(isFilled); } } + } - PlatformImpl = impl; + /// + /// Gets or sets the figures. + /// + /// + /// The points. + /// + [Content] + public Points Points + { + get => _points; + set => SetAndRaise(PointsProperty, ref _points, value); } + public bool IsFilled + { + get => GetValue(IsFilledProperty); + set => SetValue(IsFilledProperty, value); + } + + public override IGeometryImpl PlatformImpl + { + get + { + PrepareIfNeeded(); + return base.PlatformImpl; + } + protected set => base.PlatformImpl = value; + } + + private Points _points; + private bool _isDirty; + private IDisposable _pointsObserver; + /// public override Geometry Clone() { - return new PolylineGeometry(new List(_points), _isFilled); + PrepareIfNeeded(); + return new PolylineGeometry(Points, IsFilled); + } + + private void OnPointsChanged(Points oldValue, Points newValue) + { + _pointsObserver?.Dispose(); + + _pointsObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged()); + } + + internal void NotifyChanged() + { + _isDirty = true; } } } diff --git a/src/Avalonia.Visuals/Media/RectangleGeometry.cs b/src/Avalonia.Visuals/Media/RectangleGeometry.cs index ef7deaa6f6..1aa449d9e1 100644 --- a/src/Avalonia.Visuals/Media/RectangleGeometry.cs +++ b/src/Avalonia.Visuals/Media/RectangleGeometry.cs @@ -10,16 +10,51 @@ namespace Avalonia.Media /// public class RectangleGeometry : Geometry { + /// + /// Defines the property. + /// + public static readonly StyledProperty RectProperty = + AvaloniaProperty.Register(nameof(Rect)); + + public Rect Rect + { + get => GetValue(RectProperty); + set => SetValue(RectProperty, value); + } + + static RectangleGeometry() + { + RectProperty.Changed.AddClassHandler(x => x.RectChanged); + } + /// /// Initializes a new instance of the class. /// - /// The rectangle bounds. - public RectangleGeometry(Rect rect) + public RectangleGeometry() { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + PlatformImpl = factory.CreateStreamGeometry(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The rectangle bounds. + public RectangleGeometry(Rect rect) : this() + { + Rect = rect; + } + + /// + public override Geometry Clone() + { + return new RectangleGeometry(Rect); + } - using (IStreamGeometryContextImpl context = impl.Open()) + private void RectChanged(AvaloniaPropertyChangedEventArgs e) + { + var rect = (Rect)e.NewValue; + using (var context = ((IStreamGeometryImpl)PlatformImpl).Open()) { context.BeginFigure(rect.TopLeft, true); context.LineTo(rect.TopRight); @@ -27,14 +62,6 @@ namespace Avalonia.Media context.LineTo(rect.BottomLeft); context.EndFigure(true); } - - PlatformImpl = impl; - } - - /// - public override Geometry Clone() - { - return new RectangleGeometry(Bounds); } } } diff --git a/src/Avalonia.Visuals/Points.cs b/src/Avalonia.Visuals/Points.cs new file mode 100644 index 0000000000..867d3d4d24 --- /dev/null +++ b/src/Avalonia.Visuals/Points.cs @@ -0,0 +1,9 @@ +// 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.Collections; + +namespace Avalonia +{ + public sealed class Points : AvaloniaList { } +} From d9b20fada4585fe0710a0290c448ed11358944d9 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 15:51:06 +0300 Subject: [PATCH 10/16] Rect parse & type converter --- src/Avalonia.Visuals/Rect.cs | 26 +++++++++++++++++++ src/Avalonia.Visuals/RelativeRect.cs | 2 +- .../Avalonia.Markup.Xaml.csproj | 1 + .../Converters/RectTypeConverter.cs | 23 ++++++++++++++++ .../AvaloniaDefaultTypeConverters.cs | 1 + .../Media/RectTests.cs | 16 ++++++++++++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 0132c5e8a3..d562429fc7 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -481,5 +481,31 @@ namespace Avalonia _width, _height); } + + /// + /// Parses a string. + /// + /// The string. + /// The current culture. + /// The parsed . + public static Rect Parse(string s, CultureInfo culture) + { + var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()) + .ToList(); + + if (parts.Count == 4) + { + return new Rect( + double.Parse(parts[0], culture), + double.Parse(parts[1], culture), + double.Parse(parts[2], culture), + double.Parse(parts[3], culture)); + } + else + { + throw new FormatException("Invalid Rect."); + } + } } } diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index 3ce3797c49..a11f080e94 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -203,7 +203,7 @@ namespace Avalonia } else { - throw new FormatException("Invalid Rect."); + throw new FormatException("Invalid RelativeRect."); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 84c0f1ef1c..08ea6b6877 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -32,6 +32,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs new file mode 100644 index 0000000000..c9c6462f89 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs @@ -0,0 +1,23 @@ +// 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.Globalization; + +namespace Avalonia.Markup.Xaml.Converters +{ + using System.ComponentModel; + + public class RectTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return Rect.Parse((string)value, culture); + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs index f8b99f2fdf..1cf5b6a58e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs @@ -39,6 +39,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml { typeof(RelativeRect), typeof(RelativeRectTypeConverter) }, { typeof(RowDefinitions), typeof(RowDefinitionsTypeConverter) }, { typeof(Size), typeof(SizeTypeConverter) }, + { typeof(Rect), typeof(RectTypeConverter) }, { typeof(Selector), typeof(SelectorTypeConverter)}, { typeof(SolidColorBrush), typeof(BrushTypeConverter) }, { typeof(Thickness), typeof(ThicknessTypeConverter) }, diff --git a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs new file mode 100644 index 0000000000..12070bfed3 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs @@ -0,0 +1,16 @@ +using System.Globalization; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class RectTests + { + [Fact] + public void Parse_Parses() + { + var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture); + var expected = new Rect(1, 2, 3, -4); + Assert.Equal(expected, rect); + } + } +} \ No newline at end of file From 42115b6ade1f572d6fa04de8f008e0f7b66a214a Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 15:52:37 +0300 Subject: [PATCH 11/16] Fix #1107 --- src/Avalonia.Visuals/Media/PathMarkupParser.cs | 1 + tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index 9c5ffe7151..fad55ed531 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -141,6 +141,7 @@ namespace Avalonia.Media bool isLargeArc = ReadBool(reader); ReadSeparator(reader); SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise; + ReadSeparator(reader); point = ReadPoint(reader, point, relative); _context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs index f0d41680c0..3b903b4436 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs @@ -56,6 +56,7 @@ namespace Avalonia.Visuals.UnitTests.Media } [Theory] + [InlineData("F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z")] // issue #1107 [InlineData("M0 0L10 10z")] [InlineData("M50 50 L100 100 L150 50")] [InlineData("M50 50L100 100L150 50")] From b9aa086f034b1aeea93d7cab7c2f08442acab45e Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 16:18:09 +0300 Subject: [PATCH 12/16] Adding Drawing classes & render test --- samples/RenderTest/MainWindow.xaml | 1 + samples/RenderTest/Pages/DrawingPage.xaml | 132 ++++++++++++++++++ samples/RenderTest/Pages/DrawingPage.xaml.cs | 18 +++ samples/RenderTest/RenderTest.csproj | 8 ++ src/Avalonia.Controls/DrawingPresenter.cs | 58 ++++++++ src/Avalonia.Controls/Shapes/Shape.cs | 28 ++-- src/Avalonia.Visuals/Media/Drawing.cs | 9 ++ src/Avalonia.Visuals/Media/DrawingGroup.cs | 58 ++++++++ src/Avalonia.Visuals/Media/GeometryDrawing.cs | 43 ++++++ 9 files changed, 343 insertions(+), 12 deletions(-) create mode 100644 samples/RenderTest/Pages/DrawingPage.xaml create mode 100644 samples/RenderTest/Pages/DrawingPage.xaml.cs create mode 100644 src/Avalonia.Controls/DrawingPresenter.cs create mode 100644 src/Avalonia.Visuals/Media/Drawing.cs create mode 100644 src/Avalonia.Visuals/Media/DrawingGroup.cs create mode 100644 src/Avalonia.Visuals/Media/GeometryDrawing.cs diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml index 1d3001f1b1..9e9a600161 100644 --- a/samples/RenderTest/MainWindow.xaml +++ b/samples/RenderTest/MainWindow.xaml @@ -27,6 +27,7 @@ + \ No newline at end of file diff --git a/samples/RenderTest/Pages/DrawingPage.xaml b/samples/RenderTest/Pages/DrawingPage.xaml new file mode 100644 index 0000000000..a1a75b1962 --- /dev/null +++ b/samples/RenderTest/Pages/DrawingPage.xaml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/RenderTest/Pages/DrawingPage.xaml.cs b/samples/RenderTest/Pages/DrawingPage.xaml.cs new file mode 100644 index 0000000000..3bf9bd545d --- /dev/null +++ b/samples/RenderTest/Pages/DrawingPage.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace RenderTest.Pages +{ + public class DrawingPage : UserControl + { + public DrawingPage() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj index ea5c0bcc58..b7e64f4dae 100644 --- a/samples/RenderTest/RenderTest.csproj +++ b/samples/RenderTest/RenderTest.csproj @@ -51,6 +51,9 @@ App.xaml + + DrawingPage.xaml + ClippingPage.xaml @@ -178,6 +181,11 @@ Designer + + + Designer + + diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs new file mode 100644 index 0000000000..50a884d3bd --- /dev/null +++ b/src/Avalonia.Controls/DrawingPresenter.cs @@ -0,0 +1,58 @@ +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Metadata; + +namespace Avalonia.Controls +{ + public class DrawingPresenter : Control + { + public static readonly StyledProperty DrawingProperty = + AvaloniaProperty.Register(nameof(Drawing)); + + [Content] + public Drawing Drawing + { + get => GetValue(DrawingProperty); + set => SetValue(DrawingProperty, value); + } + + public static readonly StyledProperty StretchProperty = + AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); + + public Stretch Stretch + { + get => GetValue(StretchProperty); + set => SetValue(StretchProperty, value); + } + + static DrawingPresenter() + { + AffectsMeasure(DrawingProperty); + AffectsRender(DrawingProperty); + } + + private Matrix _transform = Matrix.Identity; + + protected override Size MeasureOverride(Size availableSize) + { + if (Drawing == null) return new Size(); + + var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch); + + _transform = transform; + + return size; + } + + public override void Render(DrawingContext context) + { + if (Drawing != null) + { + using (context.PushPreTransform(_transform)) + { + Drawing.Draw(context); + } + } + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 427749263a..c03f4dc563 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -155,11 +155,21 @@ namespace Avalonia.Controls.Shapes { // This should probably use GetRenderBounds(strokeThickness) but then the calculations // will multiply the stroke thickness as well, which isn't correct. - Rect shapeBounds = DefiningGeometry.Bounds; + var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch); + + if (_transform != transform) + { + _transform = transform; + _renderedGeometry = null; + } + + return size; + } + + internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch) + { Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom); Matrix translate = Matrix.Identity; - double width = Width; - double height = Height; double desiredX = availableSize.Width; double desiredY = availableSize.Height; double sx = 0.0; @@ -226,15 +236,9 @@ namespace Avalonia.Controls.Shapes break; } - var t = translate * Matrix.CreateScale(sx, sy); - - if (_transform != t) - { - _transform = t; - _renderedGeometry = null; - } - - return new Size(shapeSize.Width * sx, shapeSize.Height * sy); + var transform = translate * Matrix.CreateScale(sx, sy); + var size = new Size(shapeSize.Width * sx, shapeSize.Height * sy); + return (size, transform); } private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Visuals/Media/Drawing.cs b/src/Avalonia.Visuals/Media/Drawing.cs new file mode 100644 index 0000000000..a60c591edc --- /dev/null +++ b/src/Avalonia.Visuals/Media/Drawing.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Media +{ + public abstract class Drawing : AvaloniaObject + { + public abstract void Draw(DrawingContext context); + + public abstract Rect GetBounds(); + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/DrawingGroup.cs b/src/Avalonia.Visuals/Media/DrawingGroup.cs new file mode 100644 index 0000000000..623a4bf640 --- /dev/null +++ b/src/Avalonia.Visuals/Media/DrawingGroup.cs @@ -0,0 +1,58 @@ +using Avalonia.Collections; +using Avalonia.Metadata; + +namespace Avalonia.Media +{ + public class DrawingGroup : Drawing + { + [Content] + public AvaloniaList Children { get; } = new AvaloniaList(); + + public static readonly StyledProperty OpacityProperty = + AvaloniaProperty.Register(nameof(Opacity), 1); + + public double Opacity + { + get => GetValue(OpacityProperty); + set => SetValue(OpacityProperty, value); + } + + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); + + public Transform Transform + { + get => GetValue(TransformProperty); + set => SetValue(TransformProperty, value); + } + + public override void Draw(DrawingContext context) + { + using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) + using (context.PushOpacity(Opacity)) + { + foreach (var drawing in Children) + { + drawing.Draw(context); + } + } + } + + public override Rect GetBounds() + { + var rect = new Rect(); + + foreach (var drawing in Children) + { + rect = rect.Union(drawing.GetBounds()); + } + + if (Transform != null) + { + rect = rect.TransformToAABB(Transform.Value); + } + + return rect; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs new file mode 100644 index 0000000000..e67e853a84 --- /dev/null +++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs @@ -0,0 +1,43 @@ +namespace Avalonia.Media +{ + public class GeometryDrawing : Drawing + { + public static readonly StyledProperty GeometryProperty = + AvaloniaProperty.Register(nameof(Geometry)); + + public Geometry Geometry + { + get => GetValue(GeometryProperty); + set => SetValue(GeometryProperty, value); + } + + public static readonly StyledProperty BrushProperty = + AvaloniaProperty.Register(nameof(Brush), Brushes.Transparent); + + public IBrush Brush + { + get => GetValue(BrushProperty); + set => SetValue(BrushProperty, value); + } + + public static readonly StyledProperty PenProperty = + AvaloniaProperty.Register(nameof(Pen)); + + public Pen Pen + { + get => GetValue(PenProperty); + set => SetValue(PenProperty, value); + } + + public override void Draw(DrawingContext context) + { + context.DrawGeometry(Brush, Pen, Geometry); + } + + public override Rect GetBounds() + { + // adding the Pen's stroke thickness here could yield wrong results due to transforms + return Geometry?.GetRenderBounds(0) ?? new Rect(); + } + } +} \ No newline at end of file From 7bbbb185ded1fac3541b78986a4e52658a74a84b Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 16:26:25 +0300 Subject: [PATCH 13/16] Typo --- samples/RenderTest/Pages/DrawingPage.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/RenderTest/Pages/DrawingPage.xaml b/samples/RenderTest/Pages/DrawingPage.xaml index a1a75b1962..81181e01fc 100644 --- a/samples/RenderTest/Pages/DrawingPage.xaml +++ b/samples/RenderTest/Pages/DrawingPage.xaml @@ -64,7 +64,7 @@ + Stretch="Fill" /> + Stretch="Uniform" /> + Stretch="UniformToFill" /> From c434cb215ad240b97cda43dac8388337f527e39d Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 19:11:45 +0300 Subject: [PATCH 14/16] PR comments --- src/Avalonia.Controls/DrawingPresenter.cs | 18 +++++++++--------- src/Avalonia.Visuals/Matrix.cs | 2 +- src/Avalonia.Visuals/Media/DrawingGroup.cs | 12 ++++++------ src/Avalonia.Visuals/Media/PolylineGeometry.cs | 18 +++++++----------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs index 50a884d3bd..b366f2df14 100644 --- a/src/Avalonia.Controls/DrawingPresenter.cs +++ b/src/Avalonia.Controls/DrawingPresenter.cs @@ -6,9 +6,18 @@ namespace Avalonia.Controls { public class DrawingPresenter : Control { + static DrawingPresenter() + { + AffectsMeasure(DrawingProperty); + AffectsRender(DrawingProperty); + } + public static readonly StyledProperty DrawingProperty = AvaloniaProperty.Register(nameof(Drawing)); + public static readonly StyledProperty StretchProperty = + AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); + [Content] public Drawing Drawing { @@ -16,21 +25,12 @@ namespace Avalonia.Controls set => SetValue(DrawingProperty, value); } - public static readonly StyledProperty StretchProperty = - AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); - public Stretch Stretch { get => GetValue(StretchProperty); set => SetValue(StretchProperty, value); } - static DrawingPresenter() - { - AffectsMeasure(DrawingProperty); - AffectsRender(DrawingProperty); - } - private Matrix _transform = Matrix.Identity; protected override Size MeasureOverride(Size availableSize) diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index ee31ea9152..10549b967d 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -302,7 +302,7 @@ namespace Avalonia /// /// The string. /// The current culture. - /// The . + /// The . public static Matrix Parse(string s, CultureInfo culture) { var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) diff --git a/src/Avalonia.Visuals/Media/DrawingGroup.cs b/src/Avalonia.Visuals/Media/DrawingGroup.cs index 623a4bf640..744ff2af03 100644 --- a/src/Avalonia.Visuals/Media/DrawingGroup.cs +++ b/src/Avalonia.Visuals/Media/DrawingGroup.cs @@ -5,27 +5,27 @@ namespace Avalonia.Media { public class DrawingGroup : Drawing { - [Content] - public AvaloniaList Children { get; } = new AvaloniaList(); - public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1); + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); + public double Opacity { get => GetValue(OpacityProperty); set => SetValue(OpacityProperty, value); } - public static readonly StyledProperty TransformProperty = - AvaloniaProperty.Register(nameof(Transform)); - public Transform Transform { get => GetValue(TransformProperty); set => SetValue(TransformProperty, value); } + [Content] + public AvaloniaList Children { get; } = new AvaloniaList(); + public override void Draw(DrawingContext context) { using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) diff --git a/src/Avalonia.Visuals/Media/PolylineGeometry.cs b/src/Avalonia.Visuals/Media/PolylineGeometry.cs index c8d7ac163c..7c47e7d04d 100644 --- a/src/Avalonia.Visuals/Media/PolylineGeometry.cs +++ b/src/Avalonia.Visuals/Media/PolylineGeometry.cs @@ -26,13 +26,15 @@ namespace Avalonia.Media public static readonly AvaloniaProperty IsFilledProperty = AvaloniaProperty.Register(nameof(IsFilled)); + private Points _points; + private bool _isDirty; + private IDisposable _pointsObserver; + static PolylineGeometry() { - PointsProperty.Changed.Subscribe(onNext: v => - { - (v.Sender as PolylineGeometry)?.OnPointsChanged(v.OldValue as Points, v.NewValue as Points); - }); - IsFilledProperty.Changed.AddClassHandler(x => a => x.NotifyChanged()); + PointsProperty.Changed.AddClassHandler((s, e) => + s.OnPointsChanged(e.OldValue as Points, e.NewValue as Points)); + IsFilledProperty.Changed.AddClassHandler((s, _) => s.NotifyChanged()); } /// @@ -53,8 +55,6 @@ namespace Avalonia.Media { Points.AddRange(points); IsFilled = isFilled; - - PrepareIfNeeded(); } public void PrepareIfNeeded() @@ -109,10 +109,6 @@ namespace Avalonia.Media protected set => base.PlatformImpl = value; } - private Points _points; - private bool _isDirty; - private IDisposable _pointsObserver; - /// public override Geometry Clone() { From 578c911d112e3113b0cef437ed3432c803778b47 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 20 Aug 2017 19:23:03 +0300 Subject: [PATCH 15/16] Clipping --- src/Avalonia.Controls/DrawingPresenter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs index b366f2df14..af3665fabc 100644 --- a/src/Avalonia.Controls/DrawingPresenter.cs +++ b/src/Avalonia.Controls/DrawingPresenter.cs @@ -49,6 +49,7 @@ namespace Avalonia.Controls if (Drawing != null) { using (context.PushPreTransform(_transform)) + using (context.PushClip(Bounds)) { Drawing.Draw(context); } From 8ac3a181aac7b5131bc7ca9de02159e03b78c643 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 20 Aug 2017 19:49:19 +0200 Subject: [PATCH 16/16] Allow for a null PlatformBrush in DrawingContextImpl.DrawImage Fixes #1119 --- src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 0b46ba1c47..69b582b009 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -119,7 +119,10 @@ namespace Avalonia.Direct2D1.Media using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) { - d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D(); + if (d2dOpacityMask.PlatformBrush != null) + { + d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D(); + } _renderTarget.FillGeometry( geometry,