diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs index 78f744cea0..041d91043a 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs @@ -110,7 +110,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform { //Not supported } - + public void SetTopmost(bool value) + { + //Not supported + } } } \ No newline at end of file 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 4dbff4602f..9d657b3144 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -10,7 +10,7 @@ using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core { - internal class PropertyAccessorNode : ExpressionNode, ISettableNode + internal class PropertyAccessorNode : SettableNode { private readonly bool _enableValidation; private IPropertyAccessor _accessor; @@ -23,13 +23,17 @@ 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 { return _accessor.SetValue(value, priority); } catch { } + try + { + return _accessor.SetValue(value, priority); + } + catch { } } return false; @@ -56,7 +60,10 @@ namespace Avalonia.Data.Core () => { _accessor = accessor; - return Disposable.Create(() => _accessor = null); + return Disposable.Create(() => + { + _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..092cdbe48f --- /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 != null && LastValue.Target.Equals(value); + } + return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value); + } + + protected abstract bool SetTargetValueCore(object value, BindingPriority priority); + + public abstract Type PropertyType { get; } + } +} 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/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. /// 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