From e20930793187c95d2e5392484e0782a494e758fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 5 Jun 2020 13:54:09 +0200 Subject: [PATCH 1/6] Added nullable annotations. --- src/Avalonia.Layout/ILayoutManager.cs | 3 +++ src/Avalonia.Layout/ILayoutable.cs | 2 ++ src/Avalonia.Layout/LayoutManager.cs | 10 ++++++---- src/Avalonia.Layout/Layoutable.cs | 5 +++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index c3675c18a2..16814f2e1f 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -1,3 +1,6 @@ +using System; + +#nullable enable namespace Avalonia.Layout { diff --git a/src/Avalonia.Layout/ILayoutable.cs b/src/Avalonia.Layout/ILayoutable.cs index 5c785613a9..316a017f1d 100644 --- a/src/Avalonia.Layout/ILayoutable.cs +++ b/src/Avalonia.Layout/ILayoutable.cs @@ -1,5 +1,7 @@ using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Layout { /// diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index e8cb937997..92506fee9a 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; +#nullable enable + namespace Avalonia.Layout { /// @@ -24,7 +26,7 @@ namespace Avalonia.Layout /// public void InvalidateMeasure(ILayoutable control) { - Contract.Requires(control != null); + control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); if (!control.IsAttachedToVisualTree) @@ -45,7 +47,7 @@ namespace Avalonia.Layout /// public void InvalidateArrange(ILayoutable control) { - Contract.Requires(control != null); + control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); if (!control.IsAttachedToVisualTree) @@ -73,7 +75,7 @@ namespace Avalonia.Layout { _running = true; - Stopwatch stopwatch = null; + Stopwatch? stopwatch = null; const LogEventLevel timingLogLevel = LogEventLevel.Information; bool captureTiming = Logger.IsEnabled(timingLogLevel); @@ -117,7 +119,7 @@ namespace Avalonia.Layout if (captureTiming) { - stopwatch.Stop(); + stopwatch!.Stop(); Logger.TryGet(timingLogLevel)?.Log(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index ce5200f4a4..3b13647681 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -1,8 +1,9 @@ using System; using Avalonia.Logging; -using Avalonia.Utilities; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Layout { /// @@ -153,7 +154,7 @@ namespace Avalonia.Layout /// /// Occurs when a layout pass completes for the control. /// - public event EventHandler LayoutUpdated; + public event EventHandler? LayoutUpdated; /// /// Gets or sets the width of the element. From d46265233dbd0ee77020e9ef6ed8d27f8f2e75b5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 5 Jun 2020 14:29:20 +0200 Subject: [PATCH 2/6] Raise LayoutUpdated at the end of layout pass. This attempts to match the behavior of `LayoutUpdated` in WPF and UWP. --- build/Moq.props | 2 +- src/Avalonia.Layout/ILayoutManager.cs | 5 + src/Avalonia.Layout/LayoutManager.cs | 3 + src/Avalonia.Layout/Layoutable.cs | 54 +++++++- .../Avalonia.Controls.UnitTests/GridTests.cs | 18 ++- .../LayoutableTests.cs | 121 ++++++++++++++++++ 6 files changed, 192 insertions(+), 11 deletions(-) diff --git a/build/Moq.props b/build/Moq.props index 7de9b6b6ba..9e2fd1db5d 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index 16814f2e1f..6e63d3edbb 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -9,6 +9,11 @@ namespace Avalonia.Layout /// public interface ILayoutManager { + /// + /// Raised when the layout manager completes a layout pass. + /// + event EventHandler LayoutUpdated; + /// /// Notifies the layout manager that a control requires a measure. /// diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 92506fee9a..908758045a 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -23,6 +23,8 @@ namespace Avalonia.Layout _executeLayoutPass = ExecuteLayoutPass; } + public event EventHandler? LayoutUpdated; + /// public void InvalidateMeasure(ILayoutable control) { @@ -126,6 +128,7 @@ namespace Avalonia.Layout } _queued = false; + LayoutUpdated?.Invoke(this, EventArgs.Empty); } /// diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 3b13647681..513d3d540e 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -132,6 +132,7 @@ namespace Avalonia.Layout private bool _measuring; private Size? _previousMeasure; private Rect? _previousArrange; + private EventHandler? _layoutUpdated; /// /// Initializes static members of the class. @@ -154,7 +155,28 @@ namespace Avalonia.Layout /// /// Occurs when a layout pass completes for the control. /// - public event EventHandler? LayoutUpdated; + public event EventHandler? LayoutUpdated + { + add + { + if (_layoutUpdated is null && VisualRoot is ILayoutRoot r) + { + r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; + } + + _layoutUpdated += value; + } + + remove + { + _layoutUpdated -= value; + + if (_layoutUpdated is null && VisualRoot is ILayoutRoot r) + { + r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated; + } + } + } /// /// Gets or sets the width of the element. @@ -359,12 +381,9 @@ namespace Avalonia.Layout IsArrangeValid = true; ArrangeCore(rect); _previousArrange = rect; - - LayoutUpdated?.Invoke(this, EventArgs.Empty); } } - /// /// Called by InvalidateMeasure /// @@ -694,6 +713,26 @@ namespace Avalonia.Layout InvalidateMeasure(); } + protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTreeCore(e); + + if (_layoutUpdated is object && e.Root is ILayoutRoot r) + { + r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; + } + } + + protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTreeCore(e); + + if (_layoutUpdated is object && e.Root is ILayoutRoot r) + { + r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated; + } + } + /// protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent) { @@ -702,6 +741,13 @@ namespace Avalonia.Layout base.OnVisualParentChanged(oldParent, newParent); } + /// + /// Called when the layout manager raises a LayoutUpdated event. + /// + /// The sender. + /// The event args. + private void LayoutManagedLayoutUpdated(object sender, EventArgs e) => _layoutUpdated?.Invoke(this, e); + /// /// Tests whether any of a 's properties include negative values, /// a NaN or Infinity. diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 353bb9c98d..b3882c534b 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.UnitTests; using Xunit; using Xunit.Abstractions; @@ -1182,13 +1183,18 @@ namespace Avalonia.Controls.UnitTests foreach (var xgrids in grids) scope.Children.Add(xgrids); - var root = new Grid(); - root.UseLayoutRounding = false; - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Children.Add(scope); + var rootGrid = new Grid(); + rootGrid.UseLayoutRounding = false; + rootGrid.SetValue(Grid.IsSharedSizeScopeProperty, true); + rootGrid.Children.Add(scope); - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); + var root = new TestRoot(rootGrid) + { + Width = 50, + Height = 50, + }; + + root.LayoutManager.ExecuteInitialLayoutPass(root); PrintColumnDefinitions(grids[0]); Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth); diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs index a1c1e62f58..7013e93a43 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs @@ -203,6 +203,127 @@ namespace Avalonia.Layout.UnitTests Assert.Equal(new Rect(expectedX, 0, childWidth, 100), target.Bounds); } + [Fact] + public void LayoutUpdated_Is_Called_At_End_Of_Layout_Pass() + { + Border border1; + Border border2; + var layoutManager = new LayoutManager(); + var root = new TestRoot + { + Child = border1 = new Border + { + Child = border2 = new Border + { + } + }, + LayoutManager = layoutManager, + }; + var raised = 0; + + void ValidateBounds(object sender, EventArgs e) + { + Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds); + Assert.Equal(new Rect(0, 0, 100, 100), border2.Bounds); + ++raised; + } + + root.LayoutUpdated += ValidateBounds; + border1.LayoutUpdated += ValidateBounds; + border2.LayoutUpdated += ValidateBounds; + + root.Measure(new Size(100, 100)); + root.Arrange(new Rect(0, 0, 100, 100)); + + layoutManager.ExecuteLayoutPass(); + + Assert.Equal(3, raised); + Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds); + Assert.Equal(new Rect(0, 0, 100, 100), border2.Bounds); + } + + [Fact] + public void LayoutUpdated_Subscribes_To_LayoutManager() + { + Border target; + var layoutManager = new Mock(); + layoutManager.SetupAdd(m => m.LayoutUpdated += (sender, args) => { }); + + var root = new TestRoot + { + Child = new Border + { + Child = target = new Border(), + }, + LayoutManager = layoutManager.Object, + }; + + void Handler(object sender, EventArgs e) {} + + layoutManager.Invocations.Clear(); + target.LayoutUpdated += Handler; + + layoutManager.VerifyAdd( + x => x.LayoutUpdated += It.IsAny(), + Times.Once); + + layoutManager.Invocations.Clear(); + target.LayoutUpdated -= Handler; + + layoutManager.VerifyRemove( + x => x.LayoutUpdated -= It.IsAny(), + Times.Once); + } + + [Fact] + public void LayoutManager_LayoutUpdated_Is_Subscribed_When_Attached_To_Tree() + { + Border border1; + var layoutManager = new Mock(); + layoutManager.SetupAdd(m => m.LayoutUpdated += (sender, args) => { }); + + var root = new TestRoot + { + Child = border1 = new Border(), + LayoutManager = layoutManager.Object, + }; + + var border2 = new Border(); + border2.LayoutUpdated += (s, e) => { }; + + layoutManager.Invocations.Clear(); + border1.Child = border2; + + layoutManager.VerifyAdd( + x => x.LayoutUpdated += It.IsAny(), + Times.Once); + } + + [Fact] + public void LayoutManager_LayoutUpdated_Is_Unsubscribed_When_Detached_From_Tree() + { + Border border1; + var layoutManager = new Mock(); + layoutManager.SetupAdd(m => m.LayoutUpdated += (sender, args) => { }); + + var root = new TestRoot + { + Child = border1 = new Border(), + LayoutManager = layoutManager.Object, + }; + + var border2 = new Border(); + border2.LayoutUpdated += (s, e) => { }; + border1.Child = border2; + + layoutManager.Invocations.Clear(); + border1.Child = null; + + layoutManager.VerifyRemove( + x => x.LayoutUpdated -= It.IsAny(), + Times.Once); + } + private class TestLayoutable : Layoutable { public Size ArrangeSize { get; private set; } From c3a5c48d6fcc11903339c95597b9137c7a40b69f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 5 Jun 2020 14:52:50 +0200 Subject: [PATCH 3/6] Raise ScrollChanged when layout updated. To be consistent with WPF/UWP `ScrollChanged` should be raised when layout finishes updating, rather than when the individual properties are changed. --- src/Avalonia.Controls/ScrollViewer.cs | 65 +++++++++++-------- .../ScrollViewerTests.cs | 25 ++++++- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index c3f0dc0056..058c11c9ee 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -181,6 +181,9 @@ namespace Avalonia.Controls private Size _extent; private Vector _offset; private Size _viewport; + private Size _oldExtent; + private Vector _oldOffset; + private Size _oldViewport; private Size _largeChange; private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange); @@ -198,6 +201,7 @@ namespace Avalonia.Controls /// public ScrollViewer() { + LayoutUpdated += OnLayoutUpdated; } /// @@ -221,11 +225,9 @@ namespace Avalonia.Controls private set { - var old = _extent; - if (SetAndRaise(ExtentProperty, ref _extent, value)) { - CalculatedPropertiesChanged(extentDelta: value - old); + CalculatedPropertiesChanged(); } } } @@ -242,13 +244,11 @@ namespace Avalonia.Controls set { - var old = _offset; - value = ValidateOffset(this, value); if (SetAndRaise(OffsetProperty, ref _offset, value)) { - CalculatedPropertiesChanged(offsetDelta: value - old); + CalculatedPropertiesChanged(); } } } @@ -265,11 +265,9 @@ namespace Avalonia.Controls private set { - var old = _viewport; - if (SetAndRaise(ViewportProperty, ref _viewport, value)) { - CalculatedPropertiesChanged(viewportDelta: value - old); + CalculatedPropertiesChanged(); } } } @@ -549,10 +547,7 @@ namespace Avalonia.Controls } } - private void CalculatedPropertiesChanged( - Size extentDelta = default, - Vector offsetDelta = default, - Size viewportDelta = default) + private void CalculatedPropertiesChanged() { // Pass old values of 0 here because we don't have the old values at this point, // and it shouldn't matter as only the template uses these properies. @@ -573,20 +568,6 @@ namespace Avalonia.Controls SetAndRaise(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange)); SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport); } - - if (extentDelta != default || offsetDelta != default || viewportDelta != default) - { - using var route = BuildEventRoute(ScrollChangedEvent); - - if (route.HasHandlers) - { - var e = new ScrollChangedEventArgs( - new Vector(extentDelta.Width, extentDelta.Height), - offsetDelta, - new Vector(viewportDelta.Width, viewportDelta.Height)); - route.RaiseEvent(this, e); - } - } } protected override void OnKeyDown(KeyEventArgs e) @@ -602,5 +583,35 @@ namespace Avalonia.Controls e.Handled = true; } } + + /// + /// Called when a change in scrolling state is detected, such as a change in scroll + /// position, extent, or viewport size. + /// + /// The event args. + /// + /// If you override this method, call `base.OnScrollChanged(ScrollChangedEventArgs)` to + /// ensure that this event is raised. + /// + protected virtual void OnScrollChanged(ScrollChangedEventArgs e) + { + RaiseEvent(e); + } + + private void OnLayoutUpdated(object sender, EventArgs e) => RaiseScrollChanged(); + + private void RaiseScrollChanged() + { + var e = new ScrollChangedEventArgs( + new Vector(Extent.Width - _oldExtent.Width, Extent.Height - _oldExtent.Height), + Offset - _oldOffset, + new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height)); + + OnScrollChanged(e); + + _oldExtent = Extent; + _oldOffset = Offset; + _oldViewport = Viewport; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index 8da1e26f0d..deca3cfb75 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; +using Avalonia.UnitTests; using Moq; using Xunit; @@ -150,12 +151,15 @@ namespace Avalonia.Controls.UnitTests public void Changing_Extent_Should_Raise_ScrollChanged() { var target = new ScrollViewer(); + var root = new TestRoot(target); var raised = 0; target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); + root.LayoutManager.ExecuteInitialLayoutPass(root); + target.ScrollChanged += (s, e) => { Assert.Equal(new Vector(11, 12), e.ExtentDelta); @@ -166,20 +170,26 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ExtentProperty, new Size(111, 112)); - Assert.Equal(1, raised); + Assert.Equal(0, raised); + + root.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(1, raised); } [Fact] public void Changing_Offset_Should_Raise_ScrollChanged() { var target = new ScrollViewer(); + var root = new TestRoot(target); var raised = 0; target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); + root.LayoutManager.ExecuteInitialLayoutPass(root); + target.ScrollChanged += (s, e) => { Assert.Equal(default, e.ExtentDelta); @@ -190,20 +200,26 @@ namespace Avalonia.Controls.UnitTests target.Offset = new Vector(22, 24); - Assert.Equal(1, raised); + Assert.Equal(0, raised); + root.LayoutManager.ExecuteLayoutPass(); + + Assert.Equal(1, raised); } [Fact] public void Changing_Viewport_Should_Raise_ScrollChanged() { var target = new ScrollViewer(); + var root = new TestRoot(target); var raised = 0; target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); + root.LayoutManager.ExecuteInitialLayoutPass(root); + target.ScrollChanged += (s, e) => { Assert.Equal(default, e.ExtentDelta); @@ -214,8 +230,11 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(56, 58)); - Assert.Equal(1, raised); + Assert.Equal(0, raised); + + root.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(1, raised); } private Control CreateTemplate(ScrollViewer control, INameScope scope) From bc891efc13d742291765f0fb4584a72fdd2ebcd6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jun 2020 15:56:24 +0200 Subject: [PATCH 4/6] Don't raise ScrollChanged if nothing changed. And remove empty braces in test. --- src/Avalonia.Controls/ScrollViewer.cs | 19 +++++++++++-------- .../LayoutableTests.cs | 4 +--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 058c11c9ee..d77833b32e 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -602,16 +602,19 @@ namespace Avalonia.Controls private void RaiseScrollChanged() { - var e = new ScrollChangedEventArgs( - new Vector(Extent.Width - _oldExtent.Width, Extent.Height - _oldExtent.Height), - Offset - _oldOffset, - new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height)); + var extentDelta = new Vector(Extent.Width - _oldExtent.Width, Extent.Height - _oldExtent.Height); + var offsetDelta = Offset - _oldOffset; + var viewportDelta = new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height); - OnScrollChanged(e); + if (extentDelta != default || offsetDelta != default || viewportDelta != default) + { + var e = new ScrollChangedEventArgs(extentDelta, offsetDelta, viewportDelta); + OnScrollChanged(e); - _oldExtent = Extent; - _oldOffset = Offset; - _oldViewport = Viewport; + _oldExtent = Extent; + _oldOffset = Offset; + _oldViewport = Viewport; + } } } } diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs index 7013e93a43..a21c8d589d 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs @@ -213,9 +213,7 @@ namespace Avalonia.Layout.UnitTests { Child = border1 = new Border { - Child = border2 = new Border - { - } + Child = border2 = new Border(), }, LayoutManager = layoutManager, }; From 62bc5279ca556d22f2eb99e6cf0d1e177704fdf0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 17 Jun 2020 16:20:58 -0300 Subject: [PATCH 5/6] use nearly equals for deciding to raise scrollchanged event. --- src/Avalonia.Controls/ScrollViewer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index def947a433..aceb2761a4 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -638,7 +638,7 @@ namespace Avalonia.Controls var offsetDelta = Offset - _oldOffset; var viewportDelta = new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height); - if (extentDelta != default || offsetDelta != default || viewportDelta != default) + if (!extentDelta.NearlyEquals(default) && !offsetDelta.NearlyEquals(default) && !viewportDelta.NearlyEquals(default)) { var e = new ScrollChangedEventArgs(extentDelta, offsetDelta, viewportDelta); OnScrollChanged(e); From beb4c30107aeb67a1055b3f1b2389cbc4dffac8e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 17 Jun 2020 16:50:33 -0300 Subject: [PATCH 6/6] fix if statement logic . --- src/Avalonia.Controls/ScrollViewer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index aceb2761a4..a5f55eaa02 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -638,7 +638,7 @@ namespace Avalonia.Controls var offsetDelta = Offset - _oldOffset; var viewportDelta = new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height); - if (!extentDelta.NearlyEquals(default) && !offsetDelta.NearlyEquals(default) && !viewportDelta.NearlyEquals(default)) + if (!extentDelta.NearlyEquals(default) || !offsetDelta.NearlyEquals(default) || !viewportDelta.NearlyEquals(default)) { var e = new ScrollChangedEventArgs(extentDelta, offsetDelta, viewportDelta); OnScrollChanged(e);