From 8555fed86b7070218a07f329a90da4501b2adb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Sat, 26 Sep 2020 16:13:03 +0200 Subject: [PATCH 01/22] Add failing tests for #4733 and fix property accessor. --- .../Plugins/InpcPropertyAccessorPlugin.cs | 44 ++++++++++++++++--- .../Core/ExpressionObserverTests_Property.cs | 29 ++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 8fc2a7b77c..3bf6842cd6 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using Avalonia.Utilities; @@ -11,8 +12,11 @@ namespace Avalonia.Data.Core.Plugins /// public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin { + private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup = + new Dictionary<(Type, string), PropertyInfo>(); + /// - public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null; + public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null; /// /// Starts monitoring the value of a property on an object. @@ -30,7 +34,7 @@ namespace Avalonia.Data.Core.Plugins reference.TryGetTarget(out object instance); - var p = GetPropertyWithName(instance.GetType(), propertyName); + var p = GetFirstPropertyWithName(instance.GetType(), propertyName); if (p != null) { @@ -44,12 +48,40 @@ namespace Avalonia.Data.Core.Plugins } } - private static PropertyInfo GetPropertyWithName(Type type, string propertyName) + private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName) { - const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | - BindingFlags.Static | BindingFlags.Instance; + var key = (type, propertyName); + + if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo)) + { + propertyInfo = TryFindAndCacheProperty(type, propertyName); + } + + return propertyInfo; + } + + private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName) + { + PropertyInfo found = null; + + const BindingFlags bindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + var properties = type.GetProperties(bindingFlags); + + foreach (PropertyInfo propertyInfo in properties) + { + if (propertyInfo.Name == propertyName) + { + found = propertyInfo; + + break; + } + } + + _propertyLookup.Add((type, propertyName), found); - return type.GetProperty(propertyName, bindingFlags); + return found; } private class Accessor : PropertyAccessorBase, IWeakSubscriber diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 784046ac0b..cf98293d6d 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -600,6 +600,24 @@ namespace Avalonia.Base.UnitTests.Data.Core result); } + [Fact] + public void Should_Not_Throw_Exception_On_Duplicate_Properties() + { + // Repro of https://github.com/AvaloniaUI/Avalonia/issues/4733. + var source = new MyViewModel(); + var target = new PropertyAccessorNode("Name", false); + + target.Target = new WeakReference(source); + + var result = new List(); + + target.Subscribe(x => result.Add(x)); + } + + public class MyViewModelBase { public object Name => "Name"; } + + public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; } + private interface INext { int PropertyChangedSubscriptionCount { get; } @@ -664,6 +682,17 @@ namespace Avalonia.Base.UnitTests.Data.Core } } + private class WithNewBar : Class1 + { + private string _bar; + + public new string Bar + { + get { return _bar; } + set { _bar = value; } + } + } + private class Class3 : Class1 { } From 00dbf12cee54609d0b94d0449d2affdeab93ba18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Sat, 26 Sep 2020 16:26:39 +0200 Subject: [PATCH 02/22] Remove not needed code. --- .../Data/Core/ExpressionObserverTests_Property.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index cf98293d6d..32cdd21e04 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -682,17 +682,6 @@ namespace Avalonia.Base.UnitTests.Data.Core } } - private class WithNewBar : Class1 - { - private string _bar; - - public new string Bar - { - get { return _bar; } - set { _bar = value; } - } - } - private class Class3 : Class1 { } From c02d97cf84f2f2516114540f5aa8f4d281558094 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 29 Sep 2020 14:42:38 +0300 Subject: [PATCH 03/22] allow to create Skia drawing context from existing canvas --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 98528a128a..fd6ccd2eca 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -41,6 +41,11 @@ namespace Avalonia.Skia /// public struct CreateInfo { + /// + /// Canvas to draw to. + /// + public SKCanvas Canvas; + /// /// Surface to draw to. /// @@ -82,7 +87,7 @@ namespace Avalonia.Skia if (_grContext != null) Monitor.Enter(_grContext); Surface = createInfo.Surface; - Canvas = createInfo.Surface.Canvas; + Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas; if (Canvas == null) { From f97d43e76507f35871d4d1ed0b6064b7a5ef6f39 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 30 Sep 2020 23:51:00 +0100 Subject: [PATCH 04/22] add failing unit test. --- .../TreeViewTests.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 7022fbf4c1..f1a346e6aa 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Presenters; @@ -13,6 +14,7 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; +using ReactiveUI; using Xunit; namespace Avalonia.Controls.UnitTests @@ -454,6 +456,43 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection() + { + using (Application()) + { + var dataContext = new TestDataContext(); + + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + DataContext = dataContext + }; + + target.Bind(TreeView.ItemsProperty, new Binding("Items")); + target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem")); + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var children = target.GetLogicalChildren().ToList(); + + var selectedValues = new List(); + + dataContext.WhenAnyValue(x => x.SelectedItem) + .Subscribe(x => selectedValues.Add(x)); + + _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); + _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); + + Assert.Equal(4, selectedValues.Count); + Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray()); + } + } + [Fact] public void LogicalChildren_Should_Be_Set() { @@ -1288,5 +1327,29 @@ namespace Avalonia.Controls.UnitTests private class DerivedTreeView : TreeView { } + + private class TestDataContext : ReactiveObject + { + private int _counter; + private string _selectedItem; + + public TestDataContext() + { + Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => GenerateItem())); + } + + private string GenerateItem() => $"Item {_counter++.ToString()}"; + + public ObservableCollection Items { get; } + + public string SelectedItem + { + get { return _selectedItem; } + set + { + this.RaiseAndSetIfChanged(ref _selectedItem, value); + } + } + } } } From 0bb91da4959e46a0a35988dc0583185143f1b1f6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 1 Oct 2020 10:45:02 +0100 Subject: [PATCH 05/22] fix test. --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index f1a346e6aa..9e22c44f44 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -488,7 +488,7 @@ namespace Avalonia.Controls.UnitTests _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); - Assert.Equal(4, selectedValues.Count); + Assert.Equal(3, selectedValues.Count); Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray()); } } From ec7ae1fefb8cbe3fae2d228981cec7132dddc0e0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 1 Oct 2020 10:45:33 +0100 Subject: [PATCH 06/22] [TreeView] dont raise change events on the clear stage of setting a single selection. --- src/Avalonia.Controls/TreeView.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b4c30e0149..b9e30620bc 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -219,7 +219,9 @@ namespace Avalonia.Controls private void SelectSingleItem(object item) { + _syncingSelectedItems = true; SelectedItems.Clear(); + _syncingSelectedItems = false; SelectedItems.Add(item); } @@ -353,7 +355,7 @@ namespace Avalonia.Controls MarkItemSelected(item, true); } - if (SelectedItem == null && !_syncingSelectedItems) + if (!_syncingSelectedItems) { SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]); } From 4ca07974af61aa0e363aef8570433a9f7124eafc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 1 Oct 2020 11:06:32 +0100 Subject: [PATCH 07/22] remove line that doesnt do anything. --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 9e22c44f44..d5b3e6350c 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -478,8 +478,6 @@ namespace Avalonia.Controls.UnitTests CreateNodeDataTemplate(target); ApplyTemplates(target); - var children = target.GetLogicalChildren().ToList(); - var selectedValues = new List(); dataContext.WhenAnyValue(x => x.SelectedItem) From 3fd7892cc231e488163f10709d230ae266097dde Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 09:33:18 +0100 Subject: [PATCH 08/22] use suggested implementation. --- src/Avalonia.Controls/TreeView.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b9e30620bc..560cd9e810 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -117,10 +117,8 @@ namespace Avalonia.Controls if (value != null) { if (selectedItems.Count != 1 || selectedItems[0] != value) - { - _syncingSelectedItems = true; - SelectSingleItem(value); - _syncingSelectedItems = false; + { + SelectSingleItem(value); } } else if (SelectedItems.Count > 0) @@ -219,10 +217,13 @@ namespace Avalonia.Controls private void SelectSingleItem(object item) { + var oldValue = SelectedItem; _syncingSelectedItems = true; - SelectedItems.Clear(); - _syncingSelectedItems = false; + SelectedItems.Clear(); SelectedItems.Add(item); + _syncingSelectedItems = false; + + this.RaisePropertyChanged(SelectedItemProperty, oldValue, item); } /// @@ -355,7 +356,7 @@ namespace Avalonia.Controls MarkItemSelected(item, true); } - if (!_syncingSelectedItems) + if (SelectedItem == null && !_syncingSelectedItems) { SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]); } From 09069e28b22350e2338d5065848e89a8366a36f6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 10:00:38 +0100 Subject: [PATCH 09/22] fix implementation. --- src/Avalonia.Controls/TreeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 560cd9e810..f0e00e1d7e 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -223,7 +223,7 @@ namespace Avalonia.Controls SelectedItems.Add(item); _syncingSelectedItems = false; - this.RaisePropertyChanged(SelectedItemProperty, oldValue, item); + SetAndRaise(SelectedItemProperty, ref _selectedItem, item); } /// From e893705823862945c4cfa0316e77f8293817d59c Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Oct 2020 16:07:38 +0300 Subject: [PATCH 10/22] add a way to render to canvas with DrawingContext with public api --- .../ExternalCanvasRenderTarget.cs | 32 +++++++++++++++++++ .../Avalonia.Skia/IExternalCanvasSurface.cs | 11 +++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs create mode 100644 src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs diff --git a/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs b/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs new file mode 100644 index 0000000000..c9e779797b --- /dev/null +++ b/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs @@ -0,0 +1,32 @@ +using Avalonia.Platform; +using Avalonia.Rendering; + +namespace Avalonia.Skia +{ + internal class ExternalCanvasRenderTarget : IRenderTarget + { + private IExternalCanvasSurface _surface; + + public ExternalCanvasRenderTarget(IExternalCanvasSurface canvas) + { + _surface = canvas; + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + var createInfo = new DrawingContextImpl.CreateInfo + { + Canvas = _surface.Canvas, + Dpi = _surface.Dpi, + VisualBrushRenderer = visualBrushRenderer, + DisableTextLcdRendering = true, + }; + + return new DrawingContextImpl(createInfo); + } + + public void Dispose() + { + } + } +} diff --git a/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs b/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs new file mode 100644 index 0000000000..38347f2caa --- /dev/null +++ b/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs @@ -0,0 +1,11 @@ +using SkiaSharp; + +namespace Avalonia.Skia +{ + public interface IExternalCanvasSurface + { + SKCanvas Canvas { get; } + + Vector Dpi { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b9c1cbc673..397d358ba7 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -145,6 +145,8 @@ namespace Avalonia.Skia { if (surface is IFramebufferPlatformSurface framebufferSurface) return new FramebufferRenderTarget(framebufferSurface); + if (surface is IExternalCanvasSurface canvas) + return new ExternalCanvasRenderTarget(canvas); } throw new NotSupportedException( From a4e4d6469d686e58fc52bea1c1462bd927a0c2a8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 22:30:19 +0100 Subject: [PATCH 11/22] fix nit in test --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index d5b3e6350c..b4b14ba409 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1333,11 +1333,9 @@ namespace Avalonia.Controls.UnitTests public TestDataContext() { - Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => GenerateItem())); + Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => $"Item {i}")); } - private string GenerateItem() => $"Item {_counter++.ToString()}"; - public ObservableCollection Items { get; } public string SelectedItem From 5a675a073fbeb471bdec1c67e1ea99cc580cfb7f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 23:36:46 +0100 Subject: [PATCH 12/22] fix test. --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index b4b14ba409..b90b74d794 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1333,7 +1333,7 @@ namespace Avalonia.Controls.UnitTests public TestDataContext() { - Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => $"Item {i}")); + Items = new ObservableCollection(Enumerable.Range(0, 5).Select(i => $"Item {i}")); } public ObservableCollection Items { get; } From 40941043f37d34145576bf405f90ec190b44730a Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 12:14:10 +0300 Subject: [PATCH 13/22] Revert "add a way to render to canvas with DrawingContext with public api" This reverts commit e893705823862945c4cfa0316e77f8293817d59c. --- .../ExternalCanvasRenderTarget.cs | 32 ------------------- .../Avalonia.Skia/IExternalCanvasSurface.cs | 11 ------- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 -- 3 files changed, 45 deletions(-) delete mode 100644 src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs delete mode 100644 src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs diff --git a/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs b/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs deleted file mode 100644 index c9e779797b..0000000000 --- a/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Avalonia.Platform; -using Avalonia.Rendering; - -namespace Avalonia.Skia -{ - internal class ExternalCanvasRenderTarget : IRenderTarget - { - private IExternalCanvasSurface _surface; - - public ExternalCanvasRenderTarget(IExternalCanvasSurface canvas) - { - _surface = canvas; - } - - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) - { - var createInfo = new DrawingContextImpl.CreateInfo - { - Canvas = _surface.Canvas, - Dpi = _surface.Dpi, - VisualBrushRenderer = visualBrushRenderer, - DisableTextLcdRendering = true, - }; - - return new DrawingContextImpl(createInfo); - } - - public void Dispose() - { - } - } -} diff --git a/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs b/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs deleted file mode 100644 index 38347f2caa..0000000000 --- a/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs +++ /dev/null @@ -1,11 +0,0 @@ -using SkiaSharp; - -namespace Avalonia.Skia -{ - public interface IExternalCanvasSurface - { - SKCanvas Canvas { get; } - - Vector Dpi { get; } - } -} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 397d358ba7..b9c1cbc673 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -145,8 +145,6 @@ namespace Avalonia.Skia { if (surface is IFramebufferPlatformSurface framebufferSurface) return new FramebufferRenderTarget(framebufferSurface); - if (surface is IExternalCanvasSurface canvas) - return new ExternalCanvasRenderTarget(canvas); } throw new NotSupportedException( From 9c1e31aeced7791e6a7aba251dbc9ad9ff2cf076 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 12:22:31 +0300 Subject: [PATCH 14/22] add public api to create skia drawing context from canvas --- .../Helpers/DrawingContextHelper.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs new file mode 100644 index 0000000000..72438609d5 --- /dev/null +++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs @@ -0,0 +1,31 @@ +using Avalonia.Platform; +using Avalonia.Rendering; +using SkiaSharp; + +namespace Avalonia.Skia.Helpers +{ + public class DrawingContextHelper + { + /// + /// Wrap Skia canvas in drawing context so we can use Avalonia api to render to external skia canvas + /// this is useful in scenarios where canvas is not controlled by application, but received from another non avalonia api + /// like: SKCanvas canvas = SKDocument.BeginPage(...); + /// + /// + /// + /// + /// DrawingContext + public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi, IVisualBrushRenderer visualBrushRenderer = null) + { + var createInfo = new DrawingContextImpl.CreateInfo + { + Canvas = canvas, + Dpi = dpi, + VisualBrushRenderer = visualBrushRenderer, + DisableTextLcdRendering = true, + }; + + return new DrawingContextImpl(createInfo); + } + } +} From c941a3d1b99839ad517178f6e88235d2ec4d0cd3 Mon Sep 17 00:00:00 2001 From: amwx Date: Mon, 5 Oct 2020 15:18:26 -0500 Subject: [PATCH 15/22] Return focus to PlacementTarget on close --- src/Avalonia.Controls/Primitives/Popup.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 1e5e80d144..69fa2dc709 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -586,6 +586,19 @@ namespace Avalonia.Controls.Primitives } Closed?.Invoke(this, EventArgs.Empty); + + if(PlacementTarget != null) + { + FocusManager.Instance?.Focus(PlacementTarget); + } + else + { + var anc = this.GetLogicalAncestors().OfType().FirstOrDefault(); + if (anc != null) + { + FocusManager.Instance?.Focus(anc); + } + } } private void ListenForNonClientClick(RawInputEventArgs e) From 273f296b47d0a0e8dd39a6ccf922fa8e075f4dbd Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 6 Oct 2020 15:39:21 -0500 Subject: [PATCH 16/22] Small fix --- src/Avalonia.Controls/Primitives/Popup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 69fa2dc709..5d911d7727 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives return; } - var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType().FirstOrDefault(); + var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType(); if (placementTarget == null) { @@ -587,13 +587,13 @@ namespace Avalonia.Controls.Primitives Closed?.Invoke(this, EventArgs.Empty); - if(PlacementTarget != null) + if (PlacementTarget != null) { FocusManager.Instance?.Focus(PlacementTarget); } else { - var anc = this.GetLogicalAncestors().OfType().FirstOrDefault(); + var anc = this.FindLogicalAncestorOfType(); if (anc != null) { FocusManager.Instance?.Focus(anc); From 7f7db22004fd45f136bf7307c93ce2d52f0542d3 Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 6 Oct 2020 22:23:03 -0500 Subject: [PATCH 17/22] Only move focus if Popup had it --- src/Avalonia.Controls/Primitives/Popup.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 5d911d7727..becb489557 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -587,16 +587,23 @@ namespace Avalonia.Controls.Primitives Closed?.Invoke(this, EventArgs.Empty); - if (PlacementTarget != null) - { - FocusManager.Instance?.Focus(PlacementTarget); - } - else + var focusCheck = FocusManager.Instance?.Current; + + // Focus is set to null as part of popup closing, so we only want to + // set focus to PlacementTarget if this is the case + if (focusCheck == null) { - var anc = this.FindLogicalAncestorOfType(); - if (anc != null) + if (PlacementTarget != null) { - FocusManager.Instance?.Focus(anc); + FocusManager.Instance?.Focus(PlacementTarget); + } + else + { + var anc = this.FindLogicalAncestorOfType(); + if (anc != null) + { + FocusManager.Instance?.Focus(anc); + } } } } From 952c53cdff26960392bb5e65b1f07afa060853f1 Mon Sep 17 00:00:00 2001 From: amwx Date: Wed, 7 Oct 2020 23:06:29 -0500 Subject: [PATCH 18/22] Add tests --- .../Primitives/PopupTests.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index f032186bcd..9223b08c81 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -485,6 +485,85 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Closing_Popup_Sets_Focus_On_PlacementTarget() + { + using (CreateServicesWithFocus()) + { + var window = PreparedWindow(); + + var tb = new TextBox(); + var p = new Popup + { + PlacementTarget = window, + Child = tb + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + window.Show(); + + p.Open(); + + if (p.Host is OverlayPopupHost host) + { + //Need to measure/arrange for visual children to show up + //in OverlayPopupHost + host.Measure(Size.Infinity); + host.Arrange(new Rect(host.DesiredSize)); + } + + tb.Focus(); + + p.Close(); + + var focus = FocusManager.Instance?.Current; + Assert.True(focus == window); + } + } + + [Fact] + public void Prog_Close_Popup_NoLightDismiss_Doesnt_Move_Focus_To_PlacementTarget() + { + using (CreateServicesWithFocus()) + { + var window = PreparedWindow(); + + var windowTB = new TextBox(); + window.Content = windowTB; + + var popupTB = new TextBox(); + var p = new Popup + { + PlacementTarget = window, + IsLightDismissEnabled = false, + Child = popupTB + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + window.Show(); + + p.Open(); + + if (p.Host is OverlayPopupHost host) + { + //Need to measure/arrange for visual children to show up + //in OverlayPopupHost + host.Measure(Size.Infinity); + host.Arrange(new Rect(host.DesiredSize)); + } + + popupTB.Focus(); + + windowTB.Focus(); + + var focus = FocusManager.Instance?.Current; + + Assert.True(focus == windowTB); + + p.Close(); + + Assert.True(focus == windowTB); + } + } + private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: From 8f4059654636d52eb5ff3975ebdc5788b3e60652 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Oct 2020 11:59:28 +0100 Subject: [PATCH 19/22] fix nits --- src/Avalonia.Controls/TreeView.cs | 1 - tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index f0e00e1d7e..b2bd5ab2e5 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -217,7 +217,6 @@ namespace Avalonia.Controls private void SelectSingleItem(object item) { - var oldValue = SelectedItem; _syncingSelectedItems = true; SelectedItems.Clear(); SelectedItems.Add(item); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index b90b74d794..9253d8a07f 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1328,7 +1328,6 @@ namespace Avalonia.Controls.UnitTests private class TestDataContext : ReactiveObject { - private int _counter; private string _selectedItem; public TestDataContext() From 53eeb8e57885a795bfba9272d75c25796457f5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Tar=C4=B1k=20G=C3=BCnayd=C4=B1n?= Date: Thu, 8 Oct 2020 15:43:03 +0300 Subject: [PATCH 20/22] Fix ApiDiff.props for Turkish locale. --- build/ApiDiff.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ApiDiff.props b/build/ApiDiff.props index da82fbcc51..3d322f56d5 100644 --- a/build/ApiDiff.props +++ b/build/ApiDiff.props @@ -7,6 +7,6 @@ - + From 9eed4c974e98d840b883e3534588c09eaab333d6 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 9 Oct 2020 18:29:02 +0200 Subject: [PATCH 21/22] Make sure a GlyphRun's bounds are properly calculated --- src/Avalonia.Visuals/Media/GlyphRun.cs | 15 +++++++++++++-- .../Media/TextFormatting/TextLineImpl.cs | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index 155339b985..66a8c1dd0c 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -18,7 +18,7 @@ namespace Avalonia.Media private double _fontRenderingEmSize; private Size? _size; private int _biDiLevel; - private Point _baselineOrigin; + private Point? _baselineOrigin; private ReadOnlySlice _glyphIndices; private ReadOnlySlice _glyphAdvances; @@ -97,7 +97,9 @@ namespace Avalonia.Media { get { - return _baselineOrigin; + _baselineOrigin ??= CalculateBaselineOrigin(); + + return _baselineOrigin.Value; } set => Set(ref _baselineOrigin, value); } @@ -538,6 +540,15 @@ namespace Avalonia.Media return GlyphAdvances[index]; } + /// + /// Calculates the default baseline origin of the . + /// + /// The baseline origin. + private Point CalculateBaselineOrigin() + { + return new Point(0, -GlyphTypeface.Ascent * Scale); + } + /// /// Calculates the size of the . /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index d13b4836ea..fc98e9f6f8 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -39,7 +39,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textRun in _textRuns) { - var offsetY = LineMetrics.TextBaseline; + var offsetY = LineMetrics.TextBaseline - textRun.GlyphRun.BaselineOrigin.Y; using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY))) { From 64c1bf890e04385b601c516132346e4da12e8ef8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 11 Oct 2020 15:55:21 +0300 Subject: [PATCH 22/22] Fixed DRM output --- src/Avalonia.OpenGL/Egl/EglContext.cs | 3 ++- src/Avalonia.OpenGL/Egl/EglDisplay.cs | 22 ++++++++++++------- .../Output/DrmOutput.cs | 12 +--------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index 5365354418..249b4d547f 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -73,7 +73,8 @@ namespace Avalonia.OpenGL.Egl var old = new RestoreContext(_egl, _disp.Handle, _lock); var surf = surface ?? OffscreenSurface; _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context)) + if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero, + surf?.DangerousGetHandle() ?? IntPtr.Zero, Context)) throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); success = true; return old; diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index fd3de854f5..623364866b 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -158,15 +158,21 @@ namespace Avalonia.OpenGL.Egl var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes); if (ctx == IntPtr.Zero) throw OpenGlException.GetFormattedException("eglCreateContext", _egl); - var surf = _egl.CreatePBufferSurface(_display, _config, new[] + + var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS); + + IntPtr surf = IntPtr.Zero; + if (extensions?.Contains("EGL_KHR_surfaceless_context") != true) { - EGL_WIDTH, 1, - EGL_HEIGHT, 1, - EGL_NONE - }); - if (surf == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); - var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf), + surf = _egl.CreatePBufferSurface(_display, _config, + new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }); + if (surf == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); + } + + var rv = new EglContext(this, _egl, shareCtx, ctx, + context => + surf == IntPtr.Zero ? null : new EglSurface(this, context, surf), _version, _sampleCount, _stencilSize); return rv; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 72eed9e543..dc44d2d55f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -141,17 +141,7 @@ namespace Avalonia.LinuxFramebuffer.Output _platformGl = new EglPlatformOpenGlInterface(_eglDisplay); _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface); - - EglContext CreateContext(EglContext share) - { - var offSurf = gbm_surface_create(device, 1, 1, GbmColorFormats.GBM_FORMAT_XRGB8888, - GbmBoFlags.GBM_BO_USE_RENDERING); - if (offSurf == null) - throw new InvalidOperationException("Unable to create 1x1 sized GBM surface"); - return _eglDisplay.CreateContext(share, _platformGl.CreateWindowSurface(offSurf)); - } - - _deferredContext = CreateContext(null); + _deferredContext = _platformGl.PrimaryEglContext; using (_deferredContext.MakeCurrent(_eglSurface)) {