From 07b457d8a0021ada606699529c9ce7c42f234c1c Mon Sep 17 00:00:00 2001 From: Nelson Carrillo Date: Tue, 29 May 2018 22:34:32 -0400 Subject: [PATCH 01/30] Add support for locating custom renderers --- src/Avalonia.Visuals/Media/DrawingContext.cs | 2 +- .../Rendering/IRendererFactory.cs | 20 +++++++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 9 ++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/IRendererFactory.cs diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 1d25224b8d..962f2c1ba8 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -59,7 +59,7 @@ namespace Avalonia.Media //HACK: This is a temporary hack that is used in the render loop //to update TransformedBounds property [Obsolete("HACK for render loop, don't use")] - internal Matrix CurrentContainerTransform => _currentContainerTransform; + public Matrix CurrentContainerTransform => _currentContainerTransform; /// /// Draws a bitmap image. diff --git a/src/Avalonia.Visuals/Rendering/IRendererFactory.cs b/src/Avalonia.Visuals/Rendering/IRendererFactory.cs new file mode 100644 index 0000000000..0d7044e125 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/IRendererFactory.cs @@ -0,0 +1,20 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Rendering +{ + /// + /// Defines the interface for a renderer factory. + /// + public interface IRendererFactory + { + /// + /// Creates a renderer. + /// + /// The root visual. + /// The render loop. + IRenderer Create(IRenderRoot root, IRenderLoop renderLoop); + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7b9f8ee066..159c8386b6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -124,9 +124,12 @@ namespace Avalonia.Win32 public IRenderer CreateRenderer(IRenderRoot root) { var loop = AvaloniaLocator.Current.GetService(); - return Win32Platform.UseDeferredRendering ? - (IRenderer)new DeferredRenderer(root, loop) : - new ImmediateRenderer(root); + var customRendererFactory = AvaloniaLocator.Current.GetService(); + + if (customRendererFactory != null) + return customRendererFactory.Create(root, loop); + + return Win32Platform.UseDeferredRendering ? (IRenderer)new DeferredRenderer(root, loop) : new ImmediateRenderer(root); } public void Resize(Size value) From f596cb13bb369dd27ffba3fea74d6f857748f2f7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 31 May 2018 21:45:04 -0500 Subject: [PATCH 02/30] Don't set when value is equal to the last fetched value (invalidated on PropertyChanged event). --- .../Plugins/InpcPropertyAccessorPlugin.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index ba4e60eb74..c03a384eee 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -52,9 +52,11 @@ namespace Avalonia.Data.Core.Plugins private class Accessor : PropertyAccessorBase, IWeakSubscriber { + private static readonly object CacheInvalid = new object(); private readonly WeakReference _reference; private readonly PropertyInfo _property; private bool _eventRaised; + private object _lastValue = CacheInvalid; public Accessor(WeakReference reference, PropertyInfo property) { @@ -72,7 +74,7 @@ namespace Avalonia.Data.Core.Plugins get { var o = _reference.Target; - return (o != null) ? _property.GetValue(o) : null; + return (_lastValue = (o != null) ? _property.GetValue(o) : null); } } @@ -80,6 +82,11 @@ namespace Avalonia.Data.Core.Plugins { if (_property.CanWrite) { + if (!ShouldSet(value)) + { + return true; + } + _eventRaised = false; _property.SetValue(_reference.Target, value); @@ -94,11 +101,24 @@ namespace Avalonia.Data.Core.Plugins return false; } + private bool ShouldSet(object value) + { + if (PropertyType.IsValueType) + { + return !_lastValue.Equals(value); + } + else + { + return !Object.ReferenceEquals(_lastValue, value); + } + } + void IWeakSubscriber.OnEvent(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName)) { _eventRaised = true; + _lastValue = CacheInvalid; SendCurrentValue(); } } From 41b20f63f0db3b3a63a772e70835336ad3f29d67 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 31 May 2018 22:18:44 -0500 Subject: [PATCH 03/30] Move fix up to PropertyAccessorNode from InpcPropertyAccessorPlugin. --- .../Plugins/InpcPropertyAccessorPlugin.cs | 22 +---------- .../Data/Core/PropertyAccessorNode.cs | 37 ++++++++++++++++++- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index c03a384eee..ba4e60eb74 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -52,11 +52,9 @@ namespace Avalonia.Data.Core.Plugins private class Accessor : PropertyAccessorBase, IWeakSubscriber { - private static readonly object CacheInvalid = new object(); private readonly WeakReference _reference; private readonly PropertyInfo _property; private bool _eventRaised; - private object _lastValue = CacheInvalid; public Accessor(WeakReference reference, PropertyInfo property) { @@ -74,7 +72,7 @@ namespace Avalonia.Data.Core.Plugins get { var o = _reference.Target; - return (_lastValue = (o != null) ? _property.GetValue(o) : null); + return (o != null) ? _property.GetValue(o) : null; } } @@ -82,11 +80,6 @@ namespace Avalonia.Data.Core.Plugins { if (_property.CanWrite) { - if (!ShouldSet(value)) - { - return true; - } - _eventRaised = false; _property.SetValue(_reference.Target, value); @@ -101,24 +94,11 @@ namespace Avalonia.Data.Core.Plugins return false; } - private bool ShouldSet(object value) - { - if (PropertyType.IsValueType) - { - return !_lastValue.Equals(value); - } - else - { - return !Object.ReferenceEquals(_lastValue, value); - } - } - void IWeakSubscriber.OnEvent(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName)) { _eventRaised = true; - _lastValue = CacheInvalid; SendCurrentValue(); } } diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index 4dbff4602f..efa5b88303 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -12,8 +12,10 @@ namespace Avalonia.Data.Core { internal class PropertyAccessorNode : ExpressionNode, ISettableNode { + private static readonly object CacheInvalid = new object(); private readonly bool _enableValidation; private IPropertyAccessor _accessor; + private object _lastValue = CacheInvalid; public PropertyAccessorNode(string propertyName, bool enableValidation) { @@ -29,12 +31,32 @@ namespace Avalonia.Data.Core { if (_accessor != null) { - try { return _accessor.SetValue(value, priority); } catch { } + try + { + if (ShouldNotSet(value)) + { + return true; + } + else + { + return _accessor.SetValue(value, priority); + } + } + catch { } } return false; } + private bool ShouldNotSet(object value) + { + if (PropertyType.IsValueType) + { + return _lastValue.Equals(value); + } + return Object.ReferenceEquals(_lastValue, value); + } + protected override IObservable StartListeningCore(WeakReference reference) { var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName)); @@ -58,7 +80,18 @@ namespace Avalonia.Data.Core _accessor = accessor; return Disposable.Create(() => _accessor = null); }, - _ => accessor); + _ => accessor).Select(value => + { + if (value is BindingNotification notification) + { + _lastValue = notification.HasValue ? notification.Value : CacheInvalid; + } + else + { + _lastValue = value; + } + return value; + }); } } } From a37e24dc441f471f8f9d1896b09b5be569fc5028 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 2 Jun 2018 12:30:12 -0500 Subject: [PATCH 04/30] Add unit test. --- .../AvaloniaObjectTests_Binding.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 02fb1f11ad..65d37503b6 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -457,6 +457,17 @@ namespace Avalonia.Base.UnitTests Assert.True(target.IsAnimating(Class1.FooProperty)); } + [Fact] + public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation() + { + var target = new Class1(); + var source = new TestTwoWayBindingViewModel(); + + target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); + + Assert.False(source.SetterCalled); + } + /// /// Returns an observable that returns a single value but does not complete. /// @@ -545,5 +556,22 @@ namespace Avalonia.Base.UnitTests } } } + + private class TestTwoWayBindingViewModel + { + private double _value; + + public double Value + { + get => _value; + set + { + _value = value; + SetterCalled = true; + } + } + + public bool SetterCalled { get; private set; } + } } } \ No newline at end of file From 5ea9677cb961bbe3a0a06443eb516daf8fd86aa2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 2 Jun 2018 12:30:49 -0500 Subject: [PATCH 05/30] Use WeakReferences to make sure we aren't accidentally keeping objects alive in this new cache. --- src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index efa5b88303..c958cfdb25 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -15,7 +15,7 @@ namespace Avalonia.Data.Core private static readonly object CacheInvalid = new object(); private readonly bool _enableValidation; private IPropertyAccessor _accessor; - private object _lastValue = CacheInvalid; + private WeakReference _lastValue = null; public PropertyAccessorNode(string propertyName, bool enableValidation) { @@ -52,9 +52,9 @@ namespace Avalonia.Data.Core { if (PropertyType.IsValueType) { - return _lastValue.Equals(value); + return _lastValue?.Target.Equals(value) ?? false; } - return Object.ReferenceEquals(_lastValue, value); + return Object.ReferenceEquals(_lastValue?.Target ?? CacheInvalid, value); } protected override IObservable StartListeningCore(WeakReference reference) @@ -84,11 +84,11 @@ namespace Avalonia.Data.Core { if (value is BindingNotification notification) { - _lastValue = notification.HasValue ? notification.Value : CacheInvalid; + _lastValue = notification.HasValue ? new WeakReference(notification.Value) : null; } else { - _lastValue = value; + _lastValue = new WeakReference(value); } return value; }); From 1c3b714a0ec25166a8f3b15291f432b09da9b883 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 2 Jun 2018 13:02:13 -0500 Subject: [PATCH 06/30] Move fix up to SettableNode and ExpressionNode so all settable node types (i.e. PropertyAccessor and Indexer nodes) can get the fix. --- src/Avalonia.Base/Data/Core/ExpressionNode.cs | 6 +++ .../Data/Core/ExpressionObserver.cs | 4 +- src/Avalonia.Base/Data/Core/ISettableNode.cs | 15 ------- src/Avalonia.Base/Data/Core/IndexerNode.cs | 6 +-- .../Data/Core/PropertyAccessorNode.cs | 44 ++++--------------- src/Avalonia.Base/Data/Core/SettableNode.cs | 38 ++++++++++++++++ .../AvaloniaObjectTests_Binding.cs | 21 +++++++++ 7 files changed, 79 insertions(+), 55 deletions(-) delete mode 100644 src/Avalonia.Base/Data/Core/ISettableNode.cs create mode 100644 src/Avalonia.Base/Data/Core/SettableNode.cs diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index ae70cacdba..ac7e97a4b1 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -11,6 +11,7 @@ namespace Avalonia.Data.Core { internal abstract class ExpressionNode : ISubject { + private static readonly object CacheInvalid = new object(); protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); @@ -18,6 +19,8 @@ namespace Avalonia.Data.Core private IDisposable _valueSubscription; private IObserver _observer; + protected WeakReference LastValue { get; private set; } + public abstract string Description { get; } public ExpressionNode Next { get; set; } @@ -61,6 +64,7 @@ namespace Avalonia.Data.Core { _valueSubscription?.Dispose(); _valueSubscription = null; + LastValue = null; nextSubscription?.Dispose(); _observer = null; }); @@ -120,6 +124,7 @@ namespace Avalonia.Data.Core if (notification == null) { + LastValue = new WeakReference(value); if (Next != null) { Next.Target = new WeakReference(value); @@ -131,6 +136,7 @@ namespace Avalonia.Data.Core } else { + LastValue = new WeakReference(notification.Value); if (Next != null) { Next.Target = new WeakReference(notification.Value); diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 7719f93a02..14bc09f5b7 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -154,7 +154,7 @@ namespace Avalonia.Data.Core /// public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue) { - if (Leaf is ISettableNode settable) + if (Leaf is SettableNode settable) { var node = _node; while (node != null) @@ -188,7 +188,7 @@ namespace Avalonia.Data.Core /// Gets the type of the expression result or null if the expression could not be /// evaluated. /// - public Type ResultType => (Leaf as ISettableNode)?.PropertyType; + public Type ResultType => (Leaf as SettableNode)?.PropertyType; /// /// Gets the leaf node. diff --git a/src/Avalonia.Base/Data/Core/ISettableNode.cs b/src/Avalonia.Base/Data/Core/ISettableNode.cs deleted file mode 100644 index 7788407833..0000000000 --- a/src/Avalonia.Base/Data/Core/ISettableNode.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Data; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Data.Core -{ - interface ISettableNode - { - bool SetTargetValue(object value, BindingPriority priority); - Type PropertyType { get; } - } -} diff --git a/src/Avalonia.Base/Data/Core/IndexerNode.cs b/src/Avalonia.Base/Data/Core/IndexerNode.cs index 47e82fa2d3..633d3558ee 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNode.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNode.cs @@ -15,7 +15,7 @@ using Avalonia.Data; namespace Avalonia.Data.Core { - internal class IndexerNode : ExpressionNode, ISettableNode + internal class IndexerNode : SettableNode { public IndexerNode(IList arguments) { @@ -52,7 +52,7 @@ namespace Avalonia.Data.Core return Observable.Merge(inputs).StartWith(GetValue(target)); } - public bool SetTargetValue(object value, BindingPriority priority) + protected override bool SetTargetValueCore(object value, BindingPriority priority) { var typeInfo = Target.Target.GetType().GetTypeInfo(); var list = Target.Target as IList; @@ -154,7 +154,7 @@ namespace Avalonia.Data.Core public IList Arguments { get; } - public Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType; + public override Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType; private object GetValue(object target) { diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index c958cfdb25..9d657b3144 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -10,12 +10,10 @@ using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core { - internal class PropertyAccessorNode : ExpressionNode, ISettableNode + internal class PropertyAccessorNode : SettableNode { - private static readonly object CacheInvalid = new object(); private readonly bool _enableValidation; private IPropertyAccessor _accessor; - private WeakReference _lastValue = null; public PropertyAccessorNode(string propertyName, bool enableValidation) { @@ -25,22 +23,15 @@ namespace Avalonia.Data.Core public override string Description => PropertyName; public string PropertyName { get; } - public Type PropertyType => _accessor?.PropertyType; + public override Type PropertyType => _accessor?.PropertyType; - public bool SetTargetValue(object value, BindingPriority priority) + protected override bool SetTargetValueCore(object value, BindingPriority priority) { if (_accessor != null) { try { - if (ShouldNotSet(value)) - { - return true; - } - else - { - return _accessor.SetValue(value, priority); - } + return _accessor.SetValue(value, priority); } catch { } } @@ -48,15 +39,6 @@ namespace Avalonia.Data.Core return false; } - private bool ShouldNotSet(object value) - { - if (PropertyType.IsValueType) - { - return _lastValue?.Target.Equals(value) ?? false; - } - return Object.ReferenceEquals(_lastValue?.Target ?? CacheInvalid, value); - } - protected override IObservable StartListeningCore(WeakReference reference) { var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName)); @@ -78,20 +60,12 @@ namespace Avalonia.Data.Core () => { _accessor = accessor; - return Disposable.Create(() => _accessor = null); - }, - _ => accessor).Select(value => - { - if (value is BindingNotification notification) + return Disposable.Create(() => { - _lastValue = notification.HasValue ? new WeakReference(notification.Value) : null; - } - else - { - _lastValue = new WeakReference(value); - } - return value; - }); + _accessor = null; + }); + }, + _ => accessor); } } } diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs new file mode 100644 index 0000000000..1f6ec0bff4 --- /dev/null +++ b/src/Avalonia.Base/Data/Core/SettableNode.cs @@ -0,0 +1,38 @@ +using Avalonia.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Data.Core +{ + internal abstract class SettableNode : ExpressionNode + { + public bool SetTargetValue(object value, BindingPriority priority) + { + if (ShouldNotSet(value)) + { + return true; + } + return SetTargetValueCore(value, priority); + } + + private bool ShouldNotSet(object value) + { + if (PropertyType == null) + { + return false; + } + if (PropertyType.IsValueType) + { + return LastValue?.Target.Equals(value) ?? false; + } + return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value); + } + + protected abstract bool SetTargetValueCore(object value, BindingPriority priority); + + public abstract Type PropertyType { get; } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 65d37503b6..4638aa84a5 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -468,6 +468,17 @@ namespace Avalonia.Base.UnitTests Assert.False(source.SetterCalled); } + [Fact] + public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation_Indexer() + { + var target = new Class1(); + var source = new TestTwoWayBindingViewModel(); + + target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source }); + + Assert.False(source.SetterCalled); + } + /// /// Returns an observable that returns a single value but does not complete. /// @@ -571,6 +582,16 @@ namespace Avalonia.Base.UnitTests } } + public double this[int index] + { + get => _value; + set + { + _value = value; + SetterCalled = true; + } + } + public bool SetterCalled { get; private set; } } } From f423dbbb1de22222abf01bdfdcd9f7fec5c20d8c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 2 Jun 2018 15:45:05 -0500 Subject: [PATCH 07/30] Fix codepath where the property is a value type but the weakreference has been collected. --- src/Avalonia.Base/Data/Core/SettableNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs index 1f6ec0bff4..092cdbe48f 100644 --- a/src/Avalonia.Base/Data/Core/SettableNode.cs +++ b/src/Avalonia.Base/Data/Core/SettableNode.cs @@ -26,7 +26,7 @@ namespace Avalonia.Data.Core } if (PropertyType.IsValueType) { - return LastValue?.Target.Equals(value) ?? false; + return LastValue?.Target != null && LastValue.Target.Equals(value); } return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value); } From 0af6dd2c0a0361012220bcccb9febddd37d4531a Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Mon, 4 Jun 2018 19:55:31 +0200 Subject: [PATCH 08/30] Implement topmost on window --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 5 +++++ src/Avalonia.Controls/Window.cs | 14 ++++++++++++++ .../Remote/PreviewerWindowImpl.cs | 4 ++++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 4 ++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 6 +++++- src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 4 +++- src/OSX/Avalonia.MonoMac/WindowImpl.cs | 5 +++++ .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 8 ++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 17 +++++++++++++++++ 9 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index f1f3925133..093e9bc57c 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -55,6 +55,11 @@ namespace Avalonia.Platform /// void CanResize(bool value); + /// + /// Gets or sets whether this window appears on top of all other windows + /// + void SetTopmost(bool value); + /// /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3cbfdbd657..1943225b00 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -104,6 +104,9 @@ namespace Avalonia.Controls public static readonly StyledProperty CanResizeProperty = AvaloniaProperty.Register(nameof(CanResize), true); + public static readonly StyledProperty TopmostProperty = + AvaloniaProperty.Register(nameof(Topmost)); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -125,6 +128,8 @@ namespace Avalonia.Controls CanResizeProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); + TopmostProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue)); + WindowStateProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); } @@ -230,6 +235,15 @@ namespace Avalonia.Controls set { SetValue(CanResizeProperty, value); } } + /// + /// Gets or sets whether this window appears on top of all other windows + /// + public bool Topmost + { + get { return GetValue(TopmostProperty); } + set { SetValue(TopmostProperty, value); } + } + /// /// Gets or sets the icon of the window. /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index ef16d06b60..d9a30f0d29 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -102,5 +102,9 @@ namespace Avalonia.DesignerSupport.Remote public void CanResize(bool value) { } + + public void SetTopmost(bool value) + { + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e749d10468..43f0e0a95e 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -104,6 +104,10 @@ namespace Avalonia.DesignerSupport.Remote public void CanResize(bool value) { } + + public void SetTopmost(bool value) + { + } } class ClipboardStub : IClipboard diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 1adaf9f4e1..9cc6ba6901 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -261,10 +261,13 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_unmaximize(GtkWindow window); - + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_close(GtkWindow window); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_keep_above(GtkWindow gtkWindow, bool setting); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask); @@ -472,6 +475,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_window_maximize GtkWindowMaximize; public static D.gtk_window_unmaximize GtkWindowUnmaximize; public static D.gtk_window_close GtkWindowClose; + public static D.gtk_window_set_keep_above GtkWindowSetKeepAbove; public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag; public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag; public static D.gdk_event_request_motions GdkEventRequestMotions; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index bae34db6f3..4756d33e0f 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -81,7 +81,9 @@ namespace Avalonia.Gtk3 public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value); public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value); - + + public void SetTopmost(bool value) => Native.GtkWindowSetKeepAbove(GtkWidget, value); + class EmptyDisposable : IDisposable { diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index a2f8df6791..34a0702f8c 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -116,6 +116,11 @@ namespace Avalonia.MonoMac UpdateStyle(); } + public void SetTopmost(bool value) + { + Window.Level = value ? NSWindowLevel.Floating : NSWindowLevel.Normal; + } + public void SetTitle(string title) => Window.Title = title; class ModalDisposable : IDisposable diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 86dcec410b..daaee9636e 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -78,6 +78,14 @@ namespace Avalonia.Win32.Interop SWP_RESIZE = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER } + public static class WindowPosZOrder + { + public static readonly IntPtr HWND_BOTTOM = new IntPtr(1); + public static readonly IntPtr HWND_TOP = new IntPtr(0); + public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); + } + public enum SizeCommand { Restored, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7b9f8ee066..8c0af62c74 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -32,6 +32,7 @@ namespace Avalonia.Win32 private bool _trackingMouse; private bool _decorated = true; private bool _resizable = true; + private bool _topmost = false; private double _scaling = 1; private WindowState _showWindowState; private WindowState _lastWindowState; @@ -901,5 +902,21 @@ namespace Avalonia.Win32 _resizable = value; } + + public void SetTopmost(bool value) + { + if (value == _topmost) + { + return; + } + + IntPtr hWndInsertAfter = value ? WindowPosZOrder.HWND_TOPMOST : WindowPosZOrder.HWND_NOTOPMOST; + UnmanagedMethods.SetWindowPos(_hwnd, + hWndInsertAfter, + 0, 0, 0, 0, + SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE); + + _topmost = value; + } } } From c19971a44f49e0b2aa4d52b166d69aac956422c7 Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Mon, 4 Jun 2018 19:58:53 +0200 Subject: [PATCH 09/30] Replace relevant WindowPos magic number with variable --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 8c0af62c74..97e5aa99ec 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -839,7 +839,7 @@ namespace Avalonia.Win32 var cx = Math.Abs(monitorInfo.rcWork.right - x); var cy = Math.Abs(monitorInfo.rcWork.bottom - y); - SetWindowPos(_hwnd, new IntPtr(-2), x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); + SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); } } } From 5f67bf13be951b8e42f3aa65243e55bf035b1bc7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 5 Jun 2018 16:18:24 -0500 Subject: [PATCH 10/30] Parallelize build on Windows. --- build.cake | 1 + 1 file changed, 1 insertion(+) diff --git a/build.cake b/build.cake index 1c796e8594..df0e1ed54e 100644 --- a/build.cake +++ b/build.cake @@ -149,6 +149,7 @@ Task("Build") settings.UseToolVersion(MSBuildToolVersion.VS2017); settings.WithProperty("Windows", "True"); settings.SetNodeReuse(false); + settings.SetMaxCpuCount(0); }); } else From 2f1c94ef30358ac4a7f83a8d5616d490aeee189e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 5 Jun 2018 18:15:23 -0500 Subject: [PATCH 11/30] Update to Cake 0.28 --- build.cake | 164 ++++++++++++++++++++++-------------------- parameters.cake | 2 - tools/packages.config | 2 +- 3 files changed, 89 insertions(+), 79 deletions(-) diff --git a/build.cake b/build.cake index df0e1ed54e..efdd62ea32 100644 --- a/build.cake +++ b/build.cake @@ -34,20 +34,31 @@ using NuGet; // PARAMETERS ////////////////////////////////////////////////////////////////////// -Parameters parameters = new Parameters(Context); -Packages packages = new Packages(Context, parameters); +class AvaloniaBuildData +{ + public AvaloniaBuildData(Parameters parameters, Packages packages) + { + Parameters = parameters; + Packages = packages; + } + + public Parameters Parameters { get; } + public Packages Packages { get; } +} /////////////////////////////////////////////////////////////////////////////// // SETUP /////////////////////////////////////////////////////////////////////////////// -Setup(context => +Setup(context => { - Information("Building version {0} of Avalonia ({1}, {2}, {3}) using version {4} of Cake.", + var parameters = new Parameters(context); + var buildContext = new AvaloniaBuildData(parameters, new Packages(context, parameters)); + + Information("Building version {0} of Avalonia ({1}, {2}) using version {3} of Cake.", parameters.Version, parameters.Platform, parameters.Configuration, - parameters.Target, typeof(ICakeContext).Assembly.GetName().Version.ToString()); if (parameters.IsRunningOnAppVeyor) @@ -55,8 +66,7 @@ Setup(context => Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name); Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch); } - - Information("Target: " + parameters.Target); + Information("Target:" + context.TargetTask.Name); Information("Platform: " + parameters.Platform); Information("Configuration: " + parameters.Configuration); Information("IsLocalBuild: " + parameters.IsLocalBuild); @@ -70,13 +80,15 @@ Setup(context => Information("IsReleasable: " + parameters.IsReleasable); Information("IsMyGetRelease: " + parameters.IsMyGetRelease); Information("IsNuGetRelease: " + parameters.IsNuGetRelease); + + return buildContext; }); /////////////////////////////////////////////////////////////////////////////// // TEARDOWN /////////////////////////////////////////////////////////////////////////////// -Teardown(context => +Teardown((context, buildContext) => { Information("Finished running tasks."); }); @@ -86,19 +98,19 @@ Teardown(context => /////////////////////////////////////////////////////////////////////////////// Task("Clean") - .Does(() => + .Does(data => { - CleanDirectories(parameters.BuildDirs); - CleanDirectory(parameters.ArtifactsDir); - CleanDirectory(parameters.NugetRoot); - CleanDirectory(parameters.ZipRoot); - CleanDirectory(parameters.BinRoot); + CleanDirectories(data.Parameters.BuildDirs); + CleanDirectory(data.Parameters.ArtifactsDir); + CleanDirectory(data.Parameters.NugetRoot); + CleanDirectory(data.Parameters.ZipRoot); + CleanDirectory(data.Parameters.BinRoot); }); Task("Restore-NuGet-Packages") .IsDependentOn("Clean") - .WithCriteria(parameters.IsRunningOnWindows) - .Does(() => + .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) + .Does(data => { var maxRetryCount = 5; var toolTimeout = 2d; @@ -115,13 +127,13 @@ Task("Restore-NuGet-Packages") toolTimeout+=0.5; }}) .Execute(()=> { - NuGetRestore(parameters.MSBuildSolution, new NuGetRestoreSettings { + NuGetRestore(data.Parameters.MSBuildSolution, new NuGetRestoreSettings { ToolTimeout = TimeSpan.FromMinutes(toolTimeout) }); }); }); -void DotNetCoreBuild() +void DotNetCoreBuild(Parameters parameters) { var settings = new DotNetCoreBuildSettings { @@ -137,14 +149,14 @@ void DotNetCoreBuild() Task("Build") .IsDependentOn("Restore-NuGet-Packages") - .Does(() => + .Does(data => { - if(parameters.IsRunningOnWindows) + if(data.Parameters.IsRunningOnWindows) { - MSBuild(parameters.MSBuildSolution, settings => { - settings.SetConfiguration(parameters.Configuration); + MSBuild(data.Parameters.MSBuildSolution, settings => { + settings.SetConfiguration(data.Parameters.Configuration); settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("Platform", "\"" + parameters.Platform + "\""); + settings.WithProperty("Platform", "\"" + data.Parameters.Platform + "\""); settings.WithProperty("UseRoslynPathHack", "true"); settings.UseToolVersion(MSBuildToolVersion.VS2017); settings.WithProperty("Windows", "True"); @@ -154,7 +166,7 @@ Task("Build") } else { - DotNetCoreBuild(); + DotNetCoreBuild(data.Parameters); } }); @@ -185,66 +197,66 @@ Task("Run-Unit-Tests") .IsDependentOn("Build") .IsDependentOn("Run-Designer-Tests") .IsDependentOn("Run-Render-Tests") - .WithCriteria(() => !parameters.SkipTests) - .Does(() => { - RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false); - if (parameters.IsRunningOnWindows) + .WithCriteria((context, data) => !data.Parameters.SkipTests) + .Does(data => { + RunCoreTest("./tests/Avalonia.Base.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Controls.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Input.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Layout.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Markup.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false); + if (data.Parameters.IsRunningOnWindows) { - RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", parameters, true); + RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, true); } }); Task("Run-Designer-Tests") .IsDependentOn("Build") - .WithCriteria(() => !parameters.SkipTests) - .Does(() => { - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", parameters, false); + .WithCriteria((context, data) => !data.Parameters.SkipTests) + .Does(data => { + RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data.Parameters, false); }); Task("Run-Render-Tests") .IsDependentOn("Build") - .WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows) - .Does(() => { - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true); + .WithCriteria((context, data) => !data.Parameters.SkipTests && data.Parameters.IsRunningOnWindows) + .Does(data => { + RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data.Parameters, true); + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data.Parameters, true); }); Task("Copy-Files") .IsDependentOn("Run-Unit-Tests") - .Does(() => + .Does(data => { - CopyFiles(packages.BinFiles, parameters.BinRoot); + CopyFiles(data.Packages.BinFiles, data.Parameters.BinRoot); }); Task("Zip-Files") .IsDependentOn("Copy-Files") - .Does(() => + .Does(data => { - Zip(parameters.BinRoot, parameters.ZipCoreArtifacts); - - Zip(parameters.ZipSourceControlCatalogDesktopDirs, - parameters.ZipTargetControlCatalogDesktopDirs, - GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + - GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + - GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + - GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + - GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); + Zip(data.Parameters.BinRoot, data.Parameters.ZipCoreArtifacts); + + Zip(data.Parameters.ZipSourceControlCatalogDesktopDirs, + data.Parameters.ZipTargetControlCatalogDesktopDirs, + GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + + GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + + GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + + GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + + GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); }); Task("Create-NuGet-Packages") .IsDependentOn("Run-Unit-Tests") .IsDependentOn("Inspect") - .Does(() => + .Does(data => { - foreach(var nuspec in packages.NuspecNuGetSettings) + foreach(var nuspec in data.Packages.NuspecNuGetSettings) { NuGetPack(nuspec); } @@ -252,12 +264,12 @@ Task("Create-NuGet-Packages") Task("Publish-MyGet") .IsDependentOn("Create-NuGet-Packages") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainRepo) - .WithCriteria(() => parameters.IsMasterBranch) - .WithCriteria(() => parameters.IsMyGetRelease) - .Does(() => + .WithCriteria((context, data) => !data.Parameters.IsLocalBuild) + .WithCriteria((context, data) => !data.Parameters.IsPullRequest) + .WithCriteria((context, data) => data.Parameters.IsMainRepo) + .WithCriteria((context, data) => data.Parameters.IsMasterBranch) + .WithCriteria((context, data) => data.Parameters.IsMyGetRelease) + .Does(data => { var apiKey = EnvironmentVariable("MYGET_API_KEY"); if(string.IsNullOrEmpty(apiKey)) @@ -271,7 +283,7 @@ Task("Publish-MyGet") throw new InvalidOperationException("Could not resolve MyGet API url."); } - foreach(var nupkg in packages.NugetPackages) + foreach(var nupkg in data.Packages.NugetPackages) { NuGetPush(nupkg, new NuGetPushSettings { Source = apiUrl, @@ -286,11 +298,11 @@ Task("Publish-MyGet") Task("Publish-NuGet") .IsDependentOn("Create-NuGet-Packages") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainRepo) - .WithCriteria(() => parameters.IsNuGetRelease) - .Does(() => + .WithCriteria((context, data) => !data.Parameters.IsLocalBuild) + .WithCriteria((context, data) => !data.Parameters.IsPullRequest) + .WithCriteria((context, data) => data.Parameters.IsMainRepo) + .WithCriteria((context, data) => data.Parameters.IsNuGetRelease) + .Does(data => { var apiKey = EnvironmentVariable("NUGET_API_KEY"); if(string.IsNullOrEmpty(apiKey)) @@ -304,7 +316,7 @@ Task("Publish-NuGet") throw new InvalidOperationException("Could not resolve NuGet API url."); } - foreach(var nupkg in packages.NugetPackages) + foreach(var nupkg in data.Packages.NugetPackages) { NuGetPush(nupkg, new NuGetPushSettings { ApiKey = apiKey, @@ -318,7 +330,7 @@ Task("Publish-NuGet") }); Task("Run-Leak-Tests") - .WithCriteria(parameters.IsRunningOnWindows) + .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) .IsDependentOn("Build") .Does(() => { @@ -358,7 +370,7 @@ Task("Run-Leak-Tests") }); Task("Inspect") - .WithCriteria(parameters.IsRunningOnWindows) + .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) .IsDependentOn("Restore-NuGet-Packages") .Does(() => { @@ -396,9 +408,9 @@ Task("Inspect") Task("Package") .IsDependentOn("Create-NuGet-Packages"); -Task("Default").Does(() => +Task("Default").Does(data => { - if(parameters.IsRunningOnWindows) + if(data.Parameters.IsRunningOnWindows) RunTarget("Package"); else RunTarget("Run-Unit-Tests"); @@ -415,4 +427,4 @@ Task("Travis") // EXECUTE /////////////////////////////////////////////////////////////////////////////// -RunTarget(parameters.Target); +RunTarget(Context.Argument("target", "Default")); diff --git a/parameters.cake b/parameters.cake index 759846c658..ffd472cbd4 100644 --- a/parameters.cake +++ b/parameters.cake @@ -1,6 +1,5 @@ public class Parameters { - public string Target { get; private set; } public string Platform { get; private set; } public string Configuration { get; private set; } public bool SkipTests { get; private set; } @@ -43,7 +42,6 @@ public class Parameters var buildSystem = context.BuildSystem(); // ARGUMENTS - Target = context.Argument("target", "Default"); Platform = context.Argument("platform", "Any CPU"); Configuration = context.Argument("configuration", "Release"); SkipTests = context.HasArgument("skip-tests"); diff --git a/tools/packages.config b/tools/packages.config index e52a2c7e98..3c65df896f 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,4 +1,4 @@ - + From 8b36caf88e731b81f7522b6d1c9971f9a1c404ea Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 5 Jun 2018 18:15:57 -0500 Subject: [PATCH 12/30] Disable 'Unused' warning in Gtk/Interop/Native.cs --- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 1adaf9f4e1..b018f4db3f 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -1,4 +1,5 @@ -using System; +#pragma warning disable 649 +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; From 4169b8de0cbd6bc7d572d552ac66bbe5fa1a7701 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 00:12:55 -0500 Subject: [PATCH 13/30] Clean up assembly version conflicts when building tests. --- Avalonia.sln | 5 ++--- build/XUnit.props | 17 +---------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/Avalonia.sln b/Avalonia.sln index 9cf93e8a84..54f6f5e7e7 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -105,9 +105,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Desktop", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{52F55355-D120-42AC-8116-8410A7D602FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{52F55355-D120-42AC-8116-8410A7D602FA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}" EndProject @@ -141,7 +141,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props build\Moq.props = build\Moq.props build\NetCore.props = build\NetCore.props - build\NetFX.props = build\NetFX.props build\ReactiveUI.props = build\ReactiveUI.props build\Rx.props = build\Rx.props build\SampleApp.props = build\SampleApp.props diff --git a/build/XUnit.props b/build/XUnit.props index 15412d19e7..079565d184 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -9,21 +9,6 @@ + - - - - - true - - - - - true - - - From df593b3a8084a47e94cb94221485a5f3823f1a1c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 00:14:15 -0500 Subject: [PATCH 14/30] Get leak tests running again in the build script. They were removed at some point. Also, update the tooling used and clean up the code for running them. --- build.cake | 81 ++++++++----------- cake.config | 15 ++++ .../Avalonia.LeakTests.csproj | 8 +- .../Properties/AssemblyInfo.cs | 8 ++ .../toolproject/tool.csproj | 11 --- .../Avalonia.UnitTests.csproj | 4 +- 6 files changed, 61 insertions(+), 66 deletions(-) create mode 100644 cake.config create mode 100644 tests/Avalonia.LeakTests/Properties/AssemblyInfo.cs delete mode 100644 tests/Avalonia.LeakTests/toolproject/tool.csproj diff --git a/build.cake b/build.cake index efdd62ea32..068b388293 100644 --- a/build.cake +++ b/build.cake @@ -10,7 +10,8 @@ // TOOLS /////////////////////////////////////////////////////////////////////////////// -#tool "nuget:?package=xunit.runner.console&version=2.3.0-beta5-build3769" +#tool "nuget:?package=xunit.runner.console&version=2.3.1" +#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559" /////////////////////////////////////////////////////////////////////////////// // USINGS @@ -195,8 +196,6 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false) Task("Run-Unit-Tests") .IsDependentOn("Build") - .IsDependentOn("Run-Designer-Tests") - .IsDependentOn("Run-Render-Tests") .WithCriteria((context, data) => !data.Parameters.SkipTests) .Does(data => { RunCoreTest("./tests/Avalonia.Base.UnitTests", data.Parameters, false); @@ -229,8 +228,36 @@ Task("Run-Render-Tests") RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data.Parameters, true); }); -Task("Copy-Files") +Task("Run-Leak-Tests") + .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) + .IsDependentOn("Build") + .Does(() => + { + var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe"); + var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings + { + Arguments = new ProcessArgumentBuilder() + .Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath) + .Append("--propagate-exit-code") + .Append("--") + .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net47\\Avalonia.LeakTests.dll"), + Timeout = 120000 + }); + + if (leakTestsExitCode != 0) + { + throw new Exception("Leak Tests failed"); + } + }); + +Task("Run-Tests") .IsDependentOn("Run-Unit-Tests") + .IsDependentOn("Run-Render-Tests") + .IsDependentOn("Run-Designer-Tests") + .IsDependentOn("Run-Leak-Tests"); + +Task("Copy-Files") + .IsDependentOn("Run-Tests") .Does(data => { CopyFiles(data.Packages.BinFiles, data.Parameters.BinRoot); @@ -252,7 +279,7 @@ Task("Zip-Files") }); Task("Create-NuGet-Packages") - .IsDependentOn("Run-Unit-Tests") + .IsDependentOn("Run-Tests") .IsDependentOn("Inspect") .Does(data => { @@ -329,46 +356,6 @@ Task("Publish-NuGet") Information("Publish-NuGet Task failed, but continuing with next Task..."); }); -Task("Run-Leak-Tests") - .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) - .IsDependentOn("Build") - .Does(() => - { - DotNetCoreRestore("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj"); - DotNetBuild("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj", settings => settings.SetConfiguration("Release")); - var report = "tests\\Avalonia.LeakTests\\bin\\Release\\report.xml"; - if(System.IO.File.Exists(report)) - System.IO.File.Delete(report); - - var toolXunitConsoleX86 = Context.Tools.Resolve("xunit.console.x86.exe").FullPath; - var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo - { - FileName="tests\\Avalonia.LeakTests\\toolproject\\bin\\dotMemoryUnit.exe", - Arguments="-targetExecutable=\"" + toolXunitConsoleX86 + "\" -returnTargetExitCode -- tests\\Avalonia.LeakTests\\bin\\Release\\Avalonia.LeakTests.dll -xml tests\\Avalonia.LeakTests\\bin\\Release\\report.xml ", - UseShellExecute = false, - }); - var st = System.Diagnostics.Stopwatch.StartNew(); - while(!proc.HasExited && !System.IO.File.Exists(report)) - { - if(st.Elapsed.TotalSeconds>60) - { - Error("Timed out, probably a bug in dotMemoryUnit"); - proc.Kill(); - throw new Exception("dotMemory issue"); - } - proc.WaitForExit(100); - } - try{ - proc.Kill(); - }catch{} - var doc = System.Xml.Linq.XDocument.Load(report); - if(doc.Root.Descendants("assembly").Any(x=>x.Attribute("failed").Value.ToString() != "0")) - { - throw new Exception("Tests failed"); - } - - }); - Task("Inspect") .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) .IsDependentOn("Restore-NuGet-Packages") @@ -413,7 +400,7 @@ Task("Default").Does(data => if(data.Parameters.IsRunningOnWindows) RunTarget("Package"); else - RunTarget("Run-Unit-Tests"); + RunTarget("Run-Tests"); }); Task("AppVeyor") .IsDependentOn("Zip-Files") @@ -421,7 +408,7 @@ Task("AppVeyor") .IsDependentOn("Publish-NuGet"); Task("Travis") - .IsDependentOn("Run-Unit-Tests"); + .IsDependentOn("Run-Tests"); /////////////////////////////////////////////////////////////////////////////// // EXECUTE diff --git a/cake.config b/cake.config new file mode 100644 index 0000000000..8089cd4084 --- /dev/null +++ b/cake.config @@ -0,0 +1,15 @@ +; This is the default configuration file for Cake. +; This file was downloaded from https://github.com/cake-build/resources + +[Nuget] +Source=https://api.nuget.org/v3/index.json +UseInProcessClient=true +LoadDependencies=false + +[Paths] +Tools=./tools +Addins=./tools/Addins +Modules=./tools/Modules + +[Settings] +SkipVerification=false diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index c945db8085..7966cac845 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -1,13 +1,12 @@  - netcoreapp2.0 + net47 - @@ -19,13 +18,10 @@ - + - - - \ No newline at end of file diff --git a/tests/Avalonia.LeakTests/Properties/AssemblyInfo.cs b/tests/Avalonia.LeakTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..4cc100aa28 --- /dev/null +++ b/tests/Avalonia.LeakTests/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// 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.Reflection; +using Xunit; + +// Don't run tests in parallel. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/Avalonia.LeakTests/toolproject/tool.csproj b/tests/Avalonia.LeakTests/toolproject/tool.csproj deleted file mode 100644 index 54dbe6f17e..0000000000 --- a/tests/Avalonia.LeakTests/toolproject/tool.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - $(MSBuildThisFileDirectory)\bin - $(OutputPath) - net461 - Library - - - - - \ No newline at end of file diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 6fe61b17f7..d86b27e804 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -1,6 +1,6 @@  - netcoreapp2.0 + netcoreapp2.0;net461 false Library @@ -16,7 +16,7 @@ - + From 93774af3a445af29a359ac330abb908cee02458e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 00:14:35 -0500 Subject: [PATCH 15/30] Update Android resource files. --- .../Resources/Resource.Designer.cs | 9 ++------- .../Avalonia.Android/Resources/Resource.Designer.cs | 7 ++----- .../Resources/Resource.Designer.cs | 9 ++------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs index cee3331ba8..96f0e76fd8 100644 --- a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs +++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs @@ -28,8 +28,6 @@ namespace ControlCatalog.Android { global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName; global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello; - global::Avalonia.Android.Resource.String.library_name = global::ControlCatalog.Android.Resource.String.library_name; - global::Splat.Resource.String.library_name = global::ControlCatalog.Android.Resource.String.library_name; } public partial class Attribute @@ -96,14 +94,11 @@ namespace ControlCatalog.Android public partial class String { - // aapt resource value: 0x7f040002 - public const int ApplicationName = 2130968578; - // aapt resource value: 0x7f040001 - public const int Hello = 2130968577; + public const int ApplicationName = 2130968577; // aapt resource value: 0x7f040000 - public const int library_name = 2130968576; + public const int Hello = 2130968576; static String() { diff --git a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs b/src/Android/Avalonia.Android/Resources/Resource.Designer.cs index e66c2800d3..80cbbc51ec 100644 --- a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.Android/Resources/Resource.Designer.cs @@ -40,14 +40,11 @@ namespace Avalonia.Android public partial class String { - // aapt resource value: 0x7f020002 - public static int ApplicationName = 2130837506; - // aapt resource value: 0x7f020001 - public static int Hello = 2130837505; + public static int ApplicationName = 2130837505; // aapt resource value: 0x7f020000 - public static int library_name = 2130837504; + public static int Hello = 2130837504; static String() { diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs index 91327cf941..e171dd6162 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs @@ -28,8 +28,6 @@ namespace Avalonia.AndroidTestApplication { global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName; global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello; - global::Avalonia.Android.Resource.String.library_name = global::Avalonia.AndroidTestApplication.Resource.String.library_name; - global::Splat.Resource.String.library_name = global::Avalonia.AndroidTestApplication.Resource.String.library_name; } public partial class Attribute @@ -64,14 +62,11 @@ namespace Avalonia.AndroidTestApplication public partial class String { - // aapt resource value: 0x7f030002 - public const int ApplicationName = 2130903042; - // aapt resource value: 0x7f030001 - public const int Hello = 2130903041; + public const int ApplicationName = 2130903041; // aapt resource value: 0x7f030000 - public const int library_name = 2130903040; + public const int Hello = 2130903040; static String() { From 046322e6b04242e69ef9fdbe7f0b9938117dfd2d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 00:39:38 -0500 Subject: [PATCH 16/30] Skip all tests when -skip-tests passed to build script. --- build.cake | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/build.cake b/build.cake index 068b388293..a64fbf0fba 100644 --- a/build.cake +++ b/build.cake @@ -229,7 +229,7 @@ Task("Run-Render-Tests") }); Task("Run-Leak-Tests") - .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) + .WithCriteria((context, data) => !data.Parameters.SkipTests && data.Parameters.IsRunningOnWindows) .IsDependentOn("Build") .Does(() => { @@ -395,13 +395,6 @@ Task("Inspect") Task("Package") .IsDependentOn("Create-NuGet-Packages"); -Task("Default").Does(data => -{ - if(data.Parameters.IsRunningOnWindows) - RunTarget("Package"); - else - RunTarget("Run-Tests"); -}); Task("AppVeyor") .IsDependentOn("Zip-Files") .IsDependentOn("Publish-MyGet") @@ -414,4 +407,11 @@ Task("Travis") // EXECUTE /////////////////////////////////////////////////////////////////////////////// -RunTarget(Context.Argument("target", "Default")); +var target = Context.Argument("target", "Default"); + +if (target == "Default") +{ + target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests"; +} + +RunTarget(target); From 18f436a2c3e1ba70b573c5a1aade5746431cce8a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 01:34:24 -0500 Subject: [PATCH 17/30] Clean up unused code warnings and malformed doc comments. --- .../interop/Direct3DInteropSample/MainWindow.cs | 1 - .../Specific/Helpers/AndroidTouchEventsHelper.cs | 2 -- src/Avalonia.Animation/AnimatorKeyFrame.cs | 2 +- .../Presenters/CarouselPresenter.cs | 4 ++++ src/Avalonia.Remote.Protocol/TcpTransportBase.cs | 4 +--- src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs | 6 +++--- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 5 +---- .../FramebufferToplevelImpl.cs | 6 +++++- .../PortableXaml/TypeDescriptorExtensions.cs | 14 +++++++------- src/OSX/Avalonia.MonoMac/ClipboardImpl.cs | 3 ++- .../Media/Imaging/D2DBitmapImpl.cs | 1 + .../Media/TransformedGeometryImpl.cs | 1 + .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 4 ++-- src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs | 2 +- src/iOS/Avalonia.iOS/TopLevelImpl.cs | 3 +-- .../AvaloniaObjectTests_Threading.cs | 2 ++ .../InteractiveTests.cs | 2 +- tests/Avalonia.RenderTests/TestBase.cs | 2 ++ 18 files changed, 35 insertions(+), 29 deletions(-) diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index ffa0de0a36..19c31a3af1 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -88,7 +88,6 @@ namespace Direct3DInteropSample context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); context.ClearRenderTargetView(renderView, Color.White); - var time = 50; // Update WorldViewProj Matrix var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * Matrix.RotationZ((float) _model.RotationZ) diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index 0f90472bd0..71822a6f47 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs @@ -126,8 +126,6 @@ namespace Avalonia.Android.Platform.Specific.Helpers return e.Action != MotionEventActions.Up; } - private Paint _paint; - public void Dispose() { HandleEvents = false; diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index 875bf761c4..02457cb9aa 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -9,7 +9,7 @@ namespace Avalonia.Animation { /// /// Defines a KeyFrame that is used for - /// objects. + /// objects. /// public class AnimatorKeyFrame { diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 144174e371..0f9bfe4de0 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -115,7 +115,9 @@ namespace Avalonia.Controls.Presenters var containers = generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count); Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl)); +#pragma warning disable 4014 MoveToPage(-1, SelectedIndex); +#pragma warning restore 4014 } break; @@ -126,7 +128,9 @@ namespace Avalonia.Controls.Presenters generator.Clear(); Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl)); +#pragma warning disable 4014 MoveToPage(-1, SelectedIndex >= 0 ? SelectedIndex : 0); +#pragma warning restore 4014 } break; } diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs index 3d2bd09485..562dbdf8f9 100644 --- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -48,14 +48,12 @@ namespace Avalonia.Remote.Protocol { var cl = await server.AcceptTcpClientAsync(); AcceptNew(); - Task.Run(async () => + await Task.Run(async () => { var tcs = new TaskCompletionSource(); var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); cb(t); await tcs.Task; - - }); } catch diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index c2005c9acb..cb996867c4 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -7,7 +7,7 @@ using System.Linq; namespace Avalonia.Media.Fonts { /// - /// Represents an identifier for a + /// Represents an identifier for a /// public class FontFamilyKey { @@ -33,12 +33,12 @@ namespace Avalonia.Media.Fonts } /// - /// Location of stored font asset that belongs to a + /// Location of stored font asset that belongs to a /// public Uri Location { get; } /// - /// Optional filename for a font asset that belongs to a + /// Optional filename for a font asset that belongs to a /// public string FileName { get; } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 0ab4ef980c..971edb1364 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -21,20 +21,17 @@ namespace Avalonia.Gtk3.Interop } return true; } - - private static readonly GCHandle PinnedHandle; + private static readonly Native.D.timeout_callback PinnedHandler; static GlibTimeout() { PinnedHandler = Handler; - } public static void Add(int priority, uint interval, Func callback) { var handle = GCHandle.Alloc(callback); - //Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); Native.GTimeoutAddFull(priority, interval, PinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 0db622ba13..627b508aa8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -70,6 +70,10 @@ namespace Avalonia.LinuxFramebuffer public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action Closed { get; set; } - public event Action LostFocus; + public event Action LostFocus + { + add {} + remove {} + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs index 81525fef7d..458b09b66e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs @@ -14,13 +14,13 @@ namespace Portable.Xaml.ComponentModel /// Gets the service from ITypeDescriptorContext /// usually in TypeConverter in xaml reader context /// examples: - /// context.GetService() - /// context.GetService() - /// context.GetService() - /// context.GetService() - /// context.GetService() - /// context.GetService() - /// context.GetService() + /// context.GetService<IXamlTypeResolver>() + /// context.GetService<IXamlNamespaceResolver>() + /// context.GetService<IXamlNameProvider>() + /// context.GetService<INamespacePrefixLookup>() + /// context.GetService<IXamlSchemaContextProvider>() + /// context.GetService<IRootObjectProvider>() + /// context.GetService<IProvideValueTarget>() /// /// Service Type /// The TypeDescriptor context. diff --git a/src/OSX/Avalonia.MonoMac/ClipboardImpl.cs b/src/OSX/Avalonia.MonoMac/ClipboardImpl.cs index f7b98c0c1f..29eb3720ec 100644 --- a/src/OSX/Avalonia.MonoMac/ClipboardImpl.cs +++ b/src/OSX/Avalonia.MonoMac/ClipboardImpl.cs @@ -22,9 +22,10 @@ namespace Avalonia.MonoMac return Task.CompletedTask; } - public async Task ClearAsync() + public Task ClearAsync() { NSPasteboard.GeneralPasteboard.ClearContents(); + return Task.CompletedTask; } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index b03e022674..6713cb13be 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -19,6 +19,7 @@ namespace Avalonia.Direct2D1.Media /// Initialize a new instance of the class /// with a bitmap backed by GPU memory. /// + /// The image factory to use when saving out this bitmap. /// The GPU bitmap. /// /// This bitmap must be either from the same render target, diff --git a/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs index e0e9e340bb..c00a826d0c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs @@ -11,6 +11,7 @@ namespace Avalonia.Direct2D1.Media /// /// Initializes a new instance of the class. /// + /// The source geometry. /// An existing Direct2D . public TransformedGeometryImpl(TransformedGeometry geometry, GeometryImpl source) : base(geometry) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 86dcec410b..4095447943 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -998,7 +998,7 @@ namespace Avalonia.Win32.Interop public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] - public static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); + internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); @@ -1408,7 +1408,7 @@ namespace Avalonia.Win32.Interop [ComImport] [Guid("0000010E-0000-0000-C000-000000000046")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IOleDataObject + internal interface IOleDataObject { void GetData([In] ref FORMATETC format, out STGMEDIUM medium); void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium); diff --git a/src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs b/src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs index ef045f61bf..a6d1bf1670 100644 --- a/src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs +++ b/src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs @@ -26,7 +26,7 @@ namespace Avalonia.iOS { Tick?.Invoke(this, new EventArgs()); } - catch (Exception e) + catch (Exception) { //TODO: log } diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 5d0074e6db..3a2d2a33c6 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -26,7 +26,6 @@ namespace Avalonia.iOS { private IInputRoot _inputRoot; private readonly KeyboardEventsHelper _keyboardHelper; - private Point _position; public TopLevelImpl() { @@ -52,7 +51,7 @@ namespace Avalonia.iOS public Action Resized { get; set; } public Action ScalingChanged { get; set; } - public IPlatformHandle Handle => null; + public new IPlatformHandle Handle => null; public double Scaling => UIScreen.MainScreen.Scale; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs index 78823a370b..e067886742 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs @@ -160,7 +160,9 @@ namespace Avalonia.Base.UnitTests public bool CurrentThreadIsLoopThread { get; set; } +#pragma warning disable 67 public event Action Signaled; +#pragma warning restore 67 public void RunLoop(CancellationToken cancellationToken) { diff --git a/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs b/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs index 067cf85c3c..58ee63cea4 100644 --- a/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs +++ b/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs @@ -403,7 +403,7 @@ namespace Avalonia.Interactivity.UnitTests private class TestInteractive : Interactive { public bool ClassHandlerInvoked { get; private set; } - public string Name { get; set; } + public new string Name { get; set; } public IEnumerable Children { diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 321dbc4fbe..19413b32eb 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -161,7 +161,9 @@ namespace Avalonia.Direct2D1.RenderTests public Thread MainThread { get; set; } +#pragma warning disable 67 public event Action Signaled; +#pragma warning restore 67 public void RunLoop(CancellationToken cancellationToken) { From 38de22cf7563217bbd4ce7644604e46aa77bef48 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 01:46:32 -0500 Subject: [PATCH 18/30] Add empty event implementations for unused events. --- .../Remote/DetachableTransportConnection.cs | 6 +++++- src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs | 6 +++++- .../Remote/RemoteDesignerEntryPoint.cs | 6 +++++- src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs | 6 +++++- src/iOS/Avalonia.iOS/EmbeddableImpl.cs | 6 +++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs b/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs index 1fb10a3a7c..9c125448a0 100644 --- a/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs +++ b/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs @@ -30,6 +30,10 @@ namespace Avalonia.DesignerSupport.Remote public event Action OnMessage; - public event Action OnException; + public event Action OnException + { + add {} + remove {} + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index ef16d06b60..61bd9670f2 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -44,7 +44,11 @@ namespace Avalonia.DesignerSupport.Remote public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } public Size MaxClientSize { get; } = new Size(4096, 4096); - public event Action LostFocus; + public event Action LostFocus + { + add {} + remove {} + } protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) { diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index f5893ae69a..2857272ac1 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -125,7 +125,11 @@ namespace Avalonia.DesignerSupport.Remote class NeverClose : ICloseable { - public event EventHandler Closed; + public event EventHandler Closed + { + add {} + remove {} + } } public static void Main(string[] cmdline) diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index fb308e62b8..3580b4fcb5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -26,7 +26,11 @@ namespace Avalonia.Markup.Xaml.Styling } /// - public event EventHandler ResourcesChanged; + public event EventHandler ResourcesChanged + { + add {} + remove {} + } /// /// Gets or sets the source URL. diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs index 3d8bafeca9..7d34cf40f7 100644 --- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs +++ b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs @@ -27,6 +27,10 @@ namespace Avalonia.iOS { } - public event Action LostFocus; + public event Action LostFocus + { + add {} + remove {} + } } } From 43e6bd2d492cbeff59e06c2080d8e03cbc02171e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 14:46:54 -0500 Subject: [PATCH 19/30] Switch off from deprecated APIs in Avalonia.MonoMac. --- src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs b/src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs index 4226668119..04db728a2b 100644 --- a/src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs +++ b/src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs @@ -7,6 +7,7 @@ using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Platform; using MonoMac.AppKit; +using MonoMac.Foundation; namespace Avalonia.MonoMac { @@ -24,9 +25,9 @@ namespace Avalonia.MonoMac else { if (panel is NSOpenPanel openPanel) - tcs.SetResult(openPanel.Filenames); + tcs.SetResult(openPanel.Urls.Select(url => url.AbsoluteString).ToArray()); else - tcs.SetResult(new[] { panel.Filename }); + tcs.SetResult(new[] { panel.Url.AbsoluteString }); } panel.OrderOut(panel); keyWindow?.MakeKeyAndOrderFront(keyWindow); @@ -62,7 +63,7 @@ namespace Avalonia.MonoMac panel = new NSSavePanel(); panel.Title = panel.Title; if (dialog.InitialDirectory != null) - panel.Directory = dialog.InitialDirectory; + panel.DirectoryUrl = new NSUrl(dialog.InitialDirectory); if (dialog.InitialFileName != null) panel.NameFieldStringValue = dialog.InitialFileName; if (dialog.Filters?.Count > 0) @@ -84,7 +85,7 @@ namespace Avalonia.MonoMac CanChooseFiles = false }; if (dialog.DefaultDirectory != null) - panel.Directory = dialog.DefaultDirectory; + panel.DirectoryUrl = new NSUrl(dialog.DefaultDirectory); return (await RunPanel(panel, parent))?.FirstOrDefault(); } } From 8d004c5ae817983df64953b230e3400f8f05c3e9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 16:17:32 -0500 Subject: [PATCH 20/30] Reduce output of Inspect build step. --- build.cake | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build.cake b/build.cake index a64fbf0fba..632b572e69 100644 --- a/build.cake +++ b/build.cake @@ -366,8 +366,13 @@ Task("Inspect") "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"}; Information("Running code inspections"); - StartProcess(Context.Tools.Resolve("inspectcode.exe"), - new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" }); + var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"), + new ProcessSettings + { + Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln", + RedirectStandardOutput = true + }); + Information("Analyzing report"); var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml")); var failBuild = false; From bc882f5b1137aedd9cc85c1e79480c66528a46fa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 16:38:11 -0500 Subject: [PATCH 21/30] Add back NetFX.props framework path overriding. --- tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj | 3 ++- tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index 7966cac845..27f3223c6c 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -1,11 +1,12 @@  - net47 + net461 + diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index d86b27e804..24cc6db7f3 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -35,7 +35,5 @@ - - - + \ No newline at end of file From 08ab688edd4b5115713232b874affe62daede8dd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 16:52:15 -0500 Subject: [PATCH 22/30] Build Avalonia.DotNetFrameworkRuntime in the NetCoreOnly build. --- Avalonia.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/Avalonia.sln b/Avalonia.sln index 54f6f5e7e7..86a5411695 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1813,6 +1813,7 @@ Global {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU + {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|x86.ActiveCfg = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|x86.Build.0 = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|Any CPU.ActiveCfg = Release|Any CPU From f985fc819e57d93dc85e6b17fc65ddcc74b316d6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 17:59:34 -0500 Subject: [PATCH 23/30] Fix LeakTests path --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 632b572e69..bf3ae41b58 100644 --- a/build.cake +++ b/build.cake @@ -240,7 +240,7 @@ Task("Run-Leak-Tests") .Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath) .Append("--propagate-exit-code") .Append("--") - .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net47\\Avalonia.LeakTests.dll"), + .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"), Timeout = 120000 }); From 4ec647b870173c545a12def947d92ab44f5be515 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 6 Jun 2018 19:50:32 -0500 Subject: [PATCH 24/30] Make our unit tests support library (Avalonia.UnitTests) target netstandard2.0 so we don't have to worry about targetting .NET Framework on linux. --- Avalonia.sln | 1 - src/Avalonia.Controls/AppBuilderBase.cs | 11 +--------- .../AppBuilderTests.cs | 5 +---- .../Avalonia.DesignerSupport.TestApp.csproj | 2 +- .../DesignerSupportTests.cs | 8 ++++++++ .../FullLayoutTests.cs | 1 + .../Avalonia.UnitTests.csproj | 20 ++----------------- tests/Avalonia.UnitTests/RuntimeInfo.cs | 16 +++++++++++++++ tests/Avalonia.UnitTests/TestServices.cs | 14 +++++++++++++ tests/Avalonia.UnitTests/app.config | 11 ---------- 10 files changed, 44 insertions(+), 45 deletions(-) create mode 100644 tests/Avalonia.UnitTests/RuntimeInfo.cs delete mode 100644 tests/Avalonia.UnitTests/app.config diff --git a/Avalonia.sln b/Avalonia.sln index 86a5411695..54f6f5e7e7 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1813,7 +1813,6 @@ Global {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU - {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|x86.ActiveCfg = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Debug|x86.Build.0 = Debug|Any CPU {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 7af3deef34..51b690ece9 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -209,16 +209,7 @@ namespace Avalonia.Controls public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); - private bool CheckSetup { get; set; } = true; - - /// - /// Set this AppBuilder to ignore the setup check. Used for testing purposes. - /// - internal TAppBuilder IgnoreSetupCheck() - { - CheckSetup = false; - return Self; - } + protected virtual bool CheckSetup => true; private void SetupAvaloniaModules() { diff --git a/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs b/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs index 60c53d126c..fae08d37b7 100644 --- a/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Xunit; using Avalonia.Controls.UnitTests; using Avalonia.Platform; +using Avalonia.UnitTests; [assembly: ExportAvaloniaModule("DefaultModule", typeof(AppBuilderTests.DefaultModule))] [assembly: ExportAvaloniaModule("RenderingModule", typeof(AppBuilderTests.Direct2DModule), ForRenderingSubsystem = "Direct2D1")] @@ -65,7 +66,6 @@ namespace Avalonia.Controls.UnitTests { ResetModuleLoadStates(); AppBuilder.Configure() - .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }) .UseAvaloniaModules() @@ -82,7 +82,6 @@ namespace Avalonia.Controls.UnitTests { ResetModuleLoadStates(); var builder = AppBuilder.Configure() - .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }, "Direct2D1"); builder.UseAvaloniaModules().SetupWithoutStarting(); @@ -92,7 +91,6 @@ namespace Avalonia.Controls.UnitTests ResetModuleLoadStates(); builder = AppBuilder.Configure() - .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }, "Skia"); builder.UseAvaloniaModules().SetupWithoutStarting(); @@ -109,7 +107,6 @@ namespace Avalonia.Controls.UnitTests { ResetModuleLoadStates(); var builder = AppBuilder.Configure() - .IgnoreSetupCheck() .UseWindowingSubsystem(() => { }) .UseRenderingSubsystem(() => { }, "TBD"); builder.UseAvaloniaModules().SetupWithoutStarting(); diff --git a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj index 76017f96bd..dd33ee831d 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj +++ b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp2.0 + netcoreapp2.0 diff --git a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs index 5386877876..5220d539d9 100644 --- a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs +++ b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs @@ -19,6 +19,12 @@ namespace Avalonia.DesignerSupport.Tests public class DesignerSupportTests { private const string DesignerAppPath = "../../../../../src/tools/Avalonia.Designer.HostApp/bin/$BUILD/netcoreapp2.0/Avalonia.Designer.HostApp.dll"; + private readonly Xunit.Abstractions.ITestOutputHelper outputHelper; + + public DesignerSupportTests(Xunit.Abstractions.ITestOutputHelper outputHelper) + { + this.outputHelper = outputHelper; + } [SkippableTheory, InlineData( @@ -73,6 +79,8 @@ namespace Avalonia.DesignerSupport.Tests } else if (msg is UpdateXamlResultMessage result) { + if (result.Error != null) + outputHelper.WriteLine(result.Error); handle = result.Handle != null ? long.Parse(result.Handle) : 0; resultMessageReceivedToken.Cancel(); conn.Dispose(); diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index d4df32a4b3..053c77b911 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -21,6 +21,7 @@ using Xunit; using Avalonia.Media; using System; using System.Collections.Generic; +using Avalonia.UnitTests; namespace Avalonia.Layout.UnitTests { diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 24cc6db7f3..c189bbbe66 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -1,22 +1,10 @@  - netcoreapp2.0;net461 + netstandard2.0 false Library - - - - - - - - - - - - @@ -29,11 +17,7 @@ - - - - - + \ No newline at end of file diff --git a/tests/Avalonia.UnitTests/RuntimeInfo.cs b/tests/Avalonia.UnitTests/RuntimeInfo.cs new file mode 100644 index 0000000000..eb5781a725 --- /dev/null +++ b/tests/Avalonia.UnitTests/RuntimeInfo.cs @@ -0,0 +1,16 @@ +using Avalonia.Platform; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Avalonia.Shared.PlatformSupport +{ + internal partial class StandardRuntimePlatform : IRuntimePlatform + { + public RuntimePlatformInfo GetRuntimeInfo() + { + return new RuntimePlatformInfo(); + } + } +} diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 394dcbaa22..8414fe7cd5 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -14,6 +14,8 @@ using Avalonia.Themes.Default; using Avalonia.Rendering; using System.Reactive.Concurrency; using System.Collections.Generic; +using Avalonia.Controls; +using System.Reflection; namespace Avalonia.UnitTests { @@ -178,4 +180,16 @@ namespace Avalonia.UnitTests y => y.Open() == Mock.Of())); } } + + public class AppBuilder : AppBuilderBase + { + public AppBuilder() + : base(new StandardRuntimePlatform(), + builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType() + ?.GetTypeInfo().Assembly)) + { + } + + protected override bool CheckSetup => false; + } } diff --git a/tests/Avalonia.UnitTests/app.config b/tests/Avalonia.UnitTests/app.config deleted file mode 100644 index fa66e8c206..0000000000 --- a/tests/Avalonia.UnitTests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file From 42121ec2c15c755f113edabc31da92e4307b5578 Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Fri, 8 Jun 2018 08:05:28 +0200 Subject: [PATCH 25/30] Move topmost to WindowBase --- src/Avalonia.Controls/Platform/IWindowBaseImpl.cs | 5 +++++ src/Avalonia.Controls/Platform/IWindowImpl.cs | 5 ----- src/Avalonia.Controls/Window.cs | 14 -------------- src/Avalonia.Controls/WindowBase.cs | 14 ++++++++++++++ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 2 ++ src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 2 -- src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 1 + src/OSX/Avalonia.MonoMac/WindowImpl.cs | 5 ----- 8 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 4f7ac82df7..9ba68f584e 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -72,6 +72,11 @@ namespace Avalonia.Platform /// void SetMinMaxSize(Size minSize, Size maxSize); + /// + /// Sets whether this window appears on top of all other windows + /// + void SetTopmost(bool value); + /// /// Gets platform specific display information /// diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 093e9bc57c..f1f3925133 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -55,11 +55,6 @@ namespace Avalonia.Platform /// void CanResize(bool value); - /// - /// Gets or sets whether this window appears on top of all other windows - /// - void SetTopmost(bool value); - /// /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 1943225b00..3cbfdbd657 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -104,9 +104,6 @@ namespace Avalonia.Controls public static readonly StyledProperty CanResizeProperty = AvaloniaProperty.Register(nameof(CanResize), true); - public static readonly StyledProperty TopmostProperty = - AvaloniaProperty.Register(nameof(Topmost)); - private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -128,8 +125,6 @@ namespace Avalonia.Controls CanResizeProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); - TopmostProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue)); - WindowStateProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); } @@ -235,15 +230,6 @@ namespace Avalonia.Controls set { SetValue(CanResizeProperty, value); } } - /// - /// Gets or sets whether this window appears on top of all other windows - /// - public bool Topmost - { - get { return GetValue(TopmostProperty); } - set { SetValue(TopmostProperty, value); } - } - /// /// Gets or sets the icon of the window. /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index c427df1c26..5d66bee2f8 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -38,6 +38,9 @@ namespace Avalonia.Controls o => o.Owner, (o, v) => o.Owner = v); + public static readonly StyledProperty TopmostProperty = + AvaloniaProperty.Register(nameof(Topmost)); + private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; @@ -52,6 +55,8 @@ namespace Avalonia.Controls MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue))); + + TopmostProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue)); } public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current) @@ -124,6 +129,15 @@ namespace Avalonia.Controls set { SetAndRaise(OwnerProperty, ref _owner, value); } } + /// + /// Gets or sets whether this window appears on top of all other windows + /// + public bool Topmost + { + get { return GetValue(TopmostProperty); } + set { SetValue(TopmostProperty, value); } + } + /// /// Activates the window. /// diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 94537d3475..8a880fd306 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -416,6 +416,8 @@ namespace Avalonia.Gtk3 public void Hide() => Native.GtkWidgetHide(GtkWidget); + public void SetTopmost(bool value) => Native.GtkWindowSetKeepAbove(GtkWidget, value); + void GetGlobalPointer(out int x, out int y) { int mask; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index 4756d33e0f..a0b754c229 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -82,8 +82,6 @@ namespace Avalonia.Gtk3 public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value); - public void SetTopmost(bool value) => Native.GtkWindowSetKeepAbove(GtkWidget, value); - class EmptyDisposable : IDisposable { diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index 8cbc6cbdd8..89cef59b53 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -123,6 +123,7 @@ namespace Avalonia.MonoMac public void Hide() => Window?.OrderOut(Window); + public void SetTopmost(bool value) => Window.Level = value ? NSWindowLevel.Floating : NSWindowLevel.Normal; public void BeginMoveDrag() { diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index 34a0702f8c..a2f8df6791 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -116,11 +116,6 @@ namespace Avalonia.MonoMac UpdateStyle(); } - public void SetTopmost(bool value) - { - Window.Level = value ? NSWindowLevel.Floating : NSWindowLevel.Normal; - } - public void SetTitle(string title) => Window.Title = title; class ModalDisposable : IDisposable From 835fbe251c0ddf8cd9da75ed2d377e2575e8836a Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Fri, 8 Jun 2018 09:47:47 +0200 Subject: [PATCH 26/30] Implement Topmost property on Popup --- src/Avalonia.Controls/Primitives/Popup.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 656f3890cd..005717d681 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -70,6 +70,12 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty StaysOpenProperty = AvaloniaProperty.Register(nameof(StaysOpen), true); + /// + /// Defines the property. + /// + public static readonly StyledProperty TopmostProperty = + AvaloniaProperty.Register(nameof(Topmost)); + private bool _isOpen; private PopupRoot _popupRoot; private TopLevel _topLevel; @@ -84,6 +90,7 @@ namespace Avalonia.Controls.Primitives IsHitTestVisibleProperty.OverrideDefaultValue(false); ChildProperty.Changed.AddClassHandler(x => x.ChildChanged); IsOpenProperty.Changed.AddClassHandler(x => x.IsOpenChanged); + TopmostProperty.Changed.AddClassHandler((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue); } /// @@ -194,6 +201,15 @@ namespace Avalonia.Controls.Primitives set { SetValue(StaysOpenProperty, value); } } + /// + /// Gets or sets whether this popup appears on top of all other windows + /// + public bool Topmost + { + get { return GetValue(TopmostProperty); } + set { SetValue(TopmostProperty, value); } + } + /// /// Gets the root of the popup window. /// From 1a4fd00f2b77d0a648b00ca680227bb164c53ddd Mon Sep 17 00:00:00 2001 From: Ivan Garcia Date: Tue, 12 Jun 2018 17:25:43 -0400 Subject: [PATCH 27/30] Fix trailing newlines in textboxes when using NetCore/Skia Fixes issue where if a textbox's text ended with a newline character the additional line at the end would not render. Did this by, in these cases, adding an additional blank AvaloniaFormattedTextLine. This also fixes an issue where line widths were being measured incorrectly because the Substring() call used for that measurement was using line.Start before it was initialized for the current line. --- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 31 +++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 00f0a48a7b..13dcd9669d 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -560,13 +560,11 @@ namespace Avalonia.Skia } measured = LineBreak(Text, curOff, length, _paint, constraint, out trailingnumber); - AvaloniaFormattedTextLine line = new AvaloniaFormattedTextLine(); line.TextLength = measured; - + line.Start = curOff; subString = Text.Substring(line.Start, line.TextLength); lineWidth = _paint.MeasureText(subString); - line.Start = curOff; line.Length = measured - trailingnumber; line.Width = lineWidth; line.Height = _lineHeight; @@ -575,10 +573,33 @@ namespace Avalonia.Skia _skiaLines.Add(line); curY += _lineHeight; - curY += mLeading; - curOff += measured; + + //if this is the last line and there are trailing newline characters then + //insert a additional line + if (curOff >= length) + { + var subStringMinusNewlines = subString.TrimEnd('\n', '\r'); + var lengthDiff = subString.Length - subStringMinusNewlines.Length; + if (lengthDiff > 0) + { + AvaloniaFormattedTextLine lastLine = new AvaloniaFormattedTextLine(); + lastLine.TextLength = lengthDiff; + lastLine.Start = curOff - lengthDiff; + var lastLineSubString = Text.Substring(line.Start, line.TextLength); + var lastLineWidth = _paint.MeasureText(lastLineSubString); + lastLine.Length = 0; + lastLine.Width = lastLineWidth; + lastLine.Height = _lineHeight; + lastLine.Top = curY; + + _skiaLines.Add(lastLine); + + curY += _lineHeight; + curY += mLeading; + } + } } // Now convert to Avalonia data formats From 3d092b9fcf31bdb7e7132565b0bd9b2088018149 Mon Sep 17 00:00:00 2001 From: sdoroff Date: Tue, 12 Jun 2018 23:41:13 -0400 Subject: [PATCH 28/30] Added a Scroll event to ScrollBar The event is fired when the ScrollBar's value is changed through interaction with the ScrollBar's Track --- src/Avalonia.Controls/Primitives/ScrollBar.cs | 37 +++++++++++++ .../Primitives/ScrollEventType.cs | 53 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/Avalonia.Controls/Primitives/ScrollEventType.cs diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index 0057b15150..3ddcb06303 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -6,9 +6,21 @@ using System.Reactive; using System.Reactive.Linq; using Avalonia.Data; using Avalonia.Interactivity; +using Avalonia.Input; namespace Avalonia.Controls.Primitives { + public class ScrollEventArgs : EventArgs + { + public ScrollEventArgs(ScrollEventType eventType, double newValue) + { + ScrollEventType = eventType; + NewValue = newValue; + } + public double NewValue { get; private set; } + public ScrollEventType ScrollEventType { get; private set; } + } + /// /// A scrollbar control. /// @@ -44,6 +56,9 @@ namespace Avalonia.Controls.Primitives { PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical"); PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); + + Thumb.DragDeltaEvent.AddClassHandler(o => o.OnThumbDragDelta, RoutingStrategies.Bubble); + Thumb.DragCompletedEvent.AddClassHandler(o => o.OnThumbDragComplete, RoutingStrategies.Bubble); } /// @@ -88,6 +103,8 @@ namespace Avalonia.Controls.Primitives set { SetValue(OrientationProperty, value); } } + public event EventHandler Scroll; + /// /// Calculates whether the scrollbar should be visible. /// @@ -140,6 +157,8 @@ namespace Avalonia.Controls.Primitives _pageUpButton = e.NameScope.Find