diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 95378ed717..d8d9678a2d 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -170,10 +170,21 @@ - - + + + + + + + + + - + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index c9c7939c1c..39497f1811 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -270,6 +270,8 @@ namespace IntegrationTestApp this.Get("BasicListBox").SelectedIndex = -1; if (source?.Name == "MenuClickedMenuItemReset") this.Get("ClickedMenuItem").Text = "None"; + if (source?.Name == "ResetSliders") + this.Get("HorizontalSlider").Value = 50; if (source?.Name == "ShowTransparentWindow") ShowTransparentWindow(); if (source?.Name == "ShowTransparentPopup") diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index e16be3fa85..94955a18ae 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -2,6 +2,7 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Avalonia.Logging; using Avalonia.Rendering; using Avalonia.Threading; @@ -64,7 +65,6 @@ namespace Avalonia.Layout } _toMeasure.Enqueue(control); - _toArrange.Enqueue(control); QueueLayoutPass(); } @@ -297,6 +297,8 @@ namespace Avalonia.Layout { control.Measure(control.PreviousMeasure.Value); } + + _toArrange.Enqueue(control); } return true; @@ -313,7 +315,10 @@ namespace Avalonia.Layout return false; } - if (control.IsMeasureValid && !control.IsArrangeValid) + if (!control.IsMeasureValid) + return false; + + if (!control.IsArrangeValid) { if (control is IEmbeddedLayoutRoot embeddedRoot) control.Arrange(new Rect(embeddedRoot.AllocatedSize)); diff --git a/src/Avalonia.Controls/Automation/Peers/ThumbAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ThumbAutomationPeer.cs new file mode 100644 index 0000000000..1566370df0 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ThumbAutomationPeer.cs @@ -0,0 +1,12 @@ +using Avalonia.Automation.Peers; +using Avalonia.Controls.Primitives; + +namespace Avalonia.Controls.Automation.Peers +{ + public class ThumbAutomationPeer : ControlAutomationPeer + { + public ThumbAutomationPeer(Thumb owner) : base(owner) { } + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Thumb; + protected override bool IsContentElementCore() => false; + } +} diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index c205830bc2..993d054f87 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -1,4 +1,6 @@ using System; +using Avalonia.Automation.Peers; +using Avalonia.Controls.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Input; using Avalonia.Interactivity; @@ -45,6 +47,8 @@ namespace Avalonia.Controls.Primitives remove { RemoveHandler(DragCompletedEvent, value); } } + protected override AutomationPeer OnCreateAutomationPeer() => new ThumbAutomationPeer(this); + protected virtual void OnDragStarted(VectorEventArgs e) { } diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs index 98d2807db5..9f13520086 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs @@ -457,5 +457,39 @@ namespace Avalonia.Base.UnitTests.Layout Assert.Equal(1, layoutCount); } + + [Fact] + public void Child_Can_Invalidate_Parent_Measure_During_Arrange() + { + // Issue #11015. + // + // - Child invalidates parent measure in arrange pass + // - Parent is added to measure & arrange queues + // - Arrange pass dequeues parent + // - Measure is not valid so parent is not arranged + // - Parent is measured + // - Parent has been dequeued from arrange queue so no arrange is performed + var child = new LayoutTestControl(); + var parent = new LayoutTestControl { Child = child }; + var root = new LayoutTestRoot { Child = parent }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + child.DoArrangeOverride = (_, s) => + { + parent.InvalidateMeasure(); + return s; + }; + + child.InvalidateMeasure(); + parent.InvalidateMeasure(); + + root.LayoutManager.ExecuteLayoutPass(); + + Assert.True(child.IsMeasureValid); + Assert.True(child.IsArrangeValid); + Assert.True(parent.IsMeasureValid); + Assert.True(parent.IsArrangeValid); + } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs b/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs index e9d0a5d3a4..9d5df2fb46 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.IntegrationTests.Appium _session = fixture.Session; var tabs = _session.FindElementByAccessibilityId("MainTabs"); - var tab = tabs.FindElementByName("ScrollBarTab"); + var tab = tabs.FindElementByName("ScrollBar"); tab.Click(); } diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs index 9371a49ade..fa83ee199c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; using Xunit; @@ -15,21 +16,95 @@ namespace Avalonia.IntegrationTests.Appium _session = fixture.Session; var tabs = _session.FindElementByAccessibilityId("MainTabs"); - var tab = tabs.FindElementByName("SliderTab"); + var tab = tabs.FindElementByName("Slider"); tab.Click(); + + var reset = _session.FindElementByAccessibilityId("ResetSliders"); + reset.Click(); + } + + [Fact] + public void Horizontal_Changes_Value_Dragging_Thumb_Right() + { + var slider = _session.FindElementByAccessibilityId("HorizontalSlider"); + var thumb = slider.FindElementByAccessibilityId("thumb"); + var initialThumbRect = thumb.Rect; + + new Actions(_session).ClickAndHold(thumb).MoveByOffset(100, 0).Release().Perform(); + + var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); + var boundValue = double.Parse( + _session.FindElementByAccessibilityId("HorizontalSliderValue").Text, + CultureInfo.InvariantCulture); + + Assert.True(value > 50); + Assert.Equal(value, boundValue); + + var currentThumbRect = thumb.Rect; + Assert.True(currentThumbRect.Left > initialThumbRect.Left); + } + + [Fact] + public void Horizontal_Changes_Value_Dragging_Thumb_Left() + { + var slider = _session.FindElementByAccessibilityId("HorizontalSlider"); + var thumb = slider.FindElementByAccessibilityId("thumb"); + var initialThumbRect = thumb.Rect; + + new Actions(_session).ClickAndHold(thumb).MoveByOffset(-100, 0).Release().Perform(); + + var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); + var boundValue = double.Parse( + _session.FindElementByAccessibilityId("HorizontalSliderValue").Text, + CultureInfo.InvariantCulture); + + Assert.True(value < 50); + Assert.Equal(value, boundValue); + + var currentThumbRect = thumb.Rect; + Assert.True(currentThumbRect.Left < initialThumbRect.Left); } [Fact] - public void Changes_Value_When_Clicking_Increase_Button() + public void Horizontal_Changes_Value_When_Clicking_Increase_Button() { - var slider = _session.FindElementByAccessibilityId("Slider"); + var slider = _session.FindElementByAccessibilityId("HorizontalSlider"); + var thumb = slider.FindElementByAccessibilityId("thumb"); + var initialThumbRect = thumb.Rect; + + new Actions(_session).MoveToElement(slider, 100, 0, MoveToElementOffsetOrigin.Center).Click().Perform(); + + var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); + var boundValue = double.Parse( + _session.FindElementByAccessibilityId("HorizontalSliderValue").Text, + CultureInfo.InvariantCulture); + + Assert.True(value > 50); + Assert.Equal(value, boundValue); + + var currentThumbRect = thumb.Rect; + Assert.True(currentThumbRect.Left > initialThumbRect.Left); + } + + [Fact] + public void Horizontal_Changes_Value_When_Clicking_Decrease_Button() + { + var slider = _session.FindElementByAccessibilityId("HorizontalSlider"); + var thumb = slider.FindElementByAccessibilityId("thumb"); + var initialThumbRect = thumb.Rect; + + new Actions(_session).MoveToElement(slider, -100, 0, MoveToElementOffsetOrigin.Center).Click().Perform(); - // slider.Text gets the Slider value - Assert.True(double.Parse(slider.Text) == 30); + var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); + var boundValue = double.Parse( + _session.FindElementByAccessibilityId("HorizontalSliderValue").Text, + CultureInfo.InvariantCulture); - new Actions(_session).Click(slider).Perform(); + Assert.True(value < 50); + Assert.Equal(value, boundValue); - Assert.Equal(50, Math.Round(double.Parse(slider.Text))); + var currentThumbRect = thumb.Rect; + Assert.True(currentThumbRect.Left < initialThumbRect.Left); } } }