diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index a6f463fdcb..353e01dca7 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -153,6 +153,9 @@
+
+
+
diff --git a/src/Avalonia.Base/Layout/LayoutInformation.cs b/src/Avalonia.Base/Layout/LayoutInformation.cs
new file mode 100644
index 0000000000..9b821053a2
--- /dev/null
+++ b/src/Avalonia.Base/Layout/LayoutInformation.cs
@@ -0,0 +1,27 @@
+namespace Avalonia.Layout;
+
+///
+/// Provides access to layout information of a control.
+///
+public static class LayoutInformation
+{
+ ///
+ /// Gets the available size constraint passed in the previous layout pass.
+ ///
+ /// The control.
+ /// Previous control measure constraint, if any.
+ public static Size? GetPreviousMeasureConstraint(Layoutable control)
+ {
+ return control.PreviousMeasure;
+ }
+
+ ///
+ /// Gets the control bounds used in the previous layout arrange pass.
+ ///
+ /// The control.
+ /// Previous control arrange bounds, if any.
+ public static Rect? GetPreviousArrangeBounds(Layoutable control)
+ {
+ return control.PreviousArrange;
+ }
+}
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 775b8adddd..4a273b0291 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -323,6 +323,9 @@ namespace Avalonia.Layout
set { SetValue(UseLayoutRoundingProperty, value); }
}
+ ///
+ /// Gets the available size passed in the previous layout pass, if any.
+ ///
internal Size? PreviousMeasure => _previousMeasure;
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index e33dc999dc..98be861afa 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -48,7 +48,8 @@ namespace Avalonia.Rendering.Composition.Server
{
canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity;
- canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
+ if (AdornerIsClipped)
+ canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
}
var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
@@ -74,7 +75,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
- if (AdornedVisual != null)
+ if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index 36fd9fe709..31722974ee 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -26,6 +26,7 @@
+
diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
new file mode 100644
index 0000000000..42b15eec96
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
@@ -0,0 +1,22 @@
+using Avalonia.Automation.Peers;
+
+namespace Avalonia.Controls.Automation.Peers
+{
+ public class SliderAutomationPeer : RangeBaseAutomationPeer
+ {
+ public SliderAutomationPeer(Slider owner) : base(owner)
+ {
+ }
+
+ override protected string GetClassNameCore()
+ {
+ return "Slider";
+ }
+
+ override protected AutomationControlType GetAutomationControlTypeCore()
+ {
+ return AutomationControlType.Slider;
+ }
+
+ }
+}
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index 79719912ea..611d57a980 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -279,8 +279,11 @@ namespace Avalonia.Controls.Primitives
private void UpdateAdornedElement(Visual adorner, Visual? adorned)
{
if (adorner.CompositionVisual != null)
+ {
adorner.CompositionVisual.AdornedVisual = adorned?.CompositionVisual;
-
+ adorner.CompositionVisual.AdornerIsClipped = GetIsClipEnabled(adorner);
+ }
+
var info = adorner.GetValue(s_adornedElementInfoProperty);
if (info != null)
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index 828bf2a1fb..7de726a932 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -10,6 +10,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Automation;
+using Avalonia.Controls.Automation.Peers;
namespace Avalonia.Controls
{
@@ -380,6 +381,11 @@ namespace Avalonia.Controls
}
}
+ protected override AutomationPeer OnCreateAutomationPeer()
+ {
+ return new SliderAutomationPeer(this);
+ }
+
///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
index 5ca4ef63bf..48ebd4068e 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
+++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
@@ -20,7 +20,6 @@ namespace Avalonia.Win32.Automation
IRawElementProviderSimple,
IRawElementProviderSimple2,
IRawElementProviderFragment,
- IRawElementProviderAdviseEvents,
IInvokeProvider
{
private static Dictionary s_propertyMap = new()
@@ -47,14 +46,31 @@ namespace Avalonia.Win32.Automation
private static ConditionalWeakTable s_nodes = new();
private readonly int[] _runtimeId;
- private int _raiseFocusChanged;
- private int _raisePropertyChanged;
public AutomationNode(AutomationPeer peer)
{
_runtimeId = new int[] { 3, GetHashCode() };
Peer = peer;
s_nodes.Add(peer, this);
+ peer.ChildrenChanged += Peer_ChildrenChanged;
+ peer.PropertyChanged += Peer_PropertyChanged;
+ }
+
+ private void Peer_ChildrenChanged(object? sender, EventArgs e)
+ {
+ ChildrenChanged();
+ }
+
+ private void Peer_PropertyChanged(object? sender, AutomationPropertyChangedEventArgs e)
+ {
+ if (s_propertyMap.TryGetValue(e.Property, out var id))
+ {
+ UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(
+ this,
+ (int)id,
+ e.OldValue as IConvertible,
+ e.NewValue as IConvertible);
+ }
}
public AutomationPeer Peer { get; protected set; }
@@ -86,14 +102,6 @@ namespace Avalonia.Win32.Automation
0);
}
- public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue)
- {
- if (_raisePropertyChanged > 0 && s_propertyMap.TryGetValue(property, out var id))
- {
- UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(this, (int)id, oldValue, newValue);
- }
- }
-
[return: MarshalAs(UnmanagedType.IUnknown)]
public virtual object? GetPatternProvider(int patternId)
{
@@ -188,32 +196,6 @@ namespace Avalonia.Win32.Automation
void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu());
void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke());
- void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties)
- {
- switch ((UiaEventId)eventId)
- {
- case UiaEventId.AutomationPropertyChanged:
- ++_raisePropertyChanged;
- break;
- case UiaEventId.AutomationFocusChanged:
- ++_raiseFocusChanged;
- break;
- }
- }
-
- void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties)
- {
- switch ((UiaEventId)eventId)
- {
- case UiaEventId.AutomationPropertyChanged:
- --_raisePropertyChanged;
- break;
- case UiaEventId.AutomationFocusChanged:
- --_raiseFocusChanged;
- break;
- }
- }
-
protected void InvokeSync(Action action)
{
if (Dispatcher.UIThread.CheckAccess())
@@ -266,15 +248,6 @@ namespace Avalonia.Win32.Automation
throw new NotSupportedException();
}
- protected void RaiseFocusChanged(AutomationNode? focused)
- {
- if (_raiseFocusChanged > 0)
- {
- UiaCoreProviderApi.UiaRaiseAutomationEvent(
- focused,
- (int)UiaEventId.AutomationFocusChanged);
- }
- }
private AutomationNode? GetRoot()
{
diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
index 3d8b4995ad..ff8ff69d5e 100644
--- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
+++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
@@ -10,8 +10,11 @@ namespace Avalonia.Win32.Automation
{
[RequiresUnreferencedCode("Requires .NET COM interop")]
internal class RootAutomationNode : AutomationNode,
- IRawElementProviderFragmentRoot
+ IRawElementProviderFragmentRoot,
+ IRawElementProviderAdviseEvents
{
+ private int _raiseFocusChanged;
+
public RootAutomationNode(AutomationPeer peer)
: base(peer)
{
@@ -42,6 +45,36 @@ namespace Avalonia.Win32.Automation
return GetOrCreate(focus);
}
+ void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties)
+ {
+ switch ((UiaEventId)eventId)
+ {
+ case UiaEventId.AutomationFocusChanged:
+ ++_raiseFocusChanged;
+ break;
+ }
+ }
+
+ void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties)
+ {
+ switch ((UiaEventId)eventId)
+ {
+ case UiaEventId.AutomationFocusChanged:
+ --_raiseFocusChanged;
+ break;
+ }
+ }
+
+ protected void RaiseFocusChanged(AutomationNode? focused)
+ {
+ if (_raiseFocusChanged > 0)
+ {
+ UiaCoreProviderApi.UiaRaiseAutomationEvent(
+ focused,
+ (int)UiaEventId.AutomationFocusChanged);
+ }
+ }
+
public void FocusChanged(object? sender, EventArgs e)
{
RaiseFocusChanged(GetOrCreate(Peer.GetFocus()));
diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
new file mode 100644
index 0000000000..7fa5eb83ee
--- /dev/null
+++ b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
@@ -0,0 +1,35 @@
+using System;
+using OpenQA.Selenium.Appium;
+using OpenQA.Selenium.Interactions;
+using Xunit;
+
+namespace Avalonia.IntegrationTests.Appium
+{
+ [Collection("Default")]
+ public class SliderTests
+ {
+ private readonly AppiumDriver _session;
+
+ public SliderTests(TestAppFixture fixture)
+ {
+ _session = fixture.Session;
+
+ var tabs = _session.FindElementByAccessibilityId("MainTabs");
+ var tab = tabs.FindElementByName("SliderTab");
+ tab.Click();
+ }
+
+ [Fact]
+ public void Changes_Value_When_Clicking_Increase_Button()
+ {
+ var slider = _session.FindElementByAccessibilityId("Slider");
+
+ // slider.Text gets the Slider value
+ Assert.True(double.Parse(slider.Text) == 30);
+
+ new Actions(_session).Click(slider).Perform();
+
+ Assert.Equal(50, Math.Round(double.Parse(slider.Text)));
+ }
+ }
+}
diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
index c0159aecff..b158bf798d 100644
--- a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
+++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@@ -18,56 +19,70 @@ public class AdornerTests : TestBase
{
}
- [Fact]
- public async Task Focus_Adorner_Is_Properly_Clipped()
+ async Task CheckAdornedContent(Control content, Control adorned, Control adorner, int width = 200, int height = 200,
+ [CallerMemberName] string testName = "")
{
- Border adorned;
var tree = new Decorator
{
Child = new VisualLayerManager
{
- Child = new Border
- {
- Background = Brushes.Red,
- Padding = new Thickness(10, 50, 10,10),
- Child = new Border()
- {
- Background = Brushes.White,
- ClipToBounds = true,
- Padding = new Thickness(0, -30, 0, 0),
- Child = adorned = new Border
- {
- Background = Brushes.Green,
- VerticalAlignment = VerticalAlignment.Top,
- Height = 100,
- Width = 50
- }
- }
- }
+ Child = content
},
- Width = 200,
- Height = 200
- };
- var adorner = new Border
- {
- BorderThickness = new Thickness(2),
- BorderBrush = Brushes.Black
+ Width = width,
+ Height = height
};
-
+
var size = new Size(tree.Width, tree.Height);
tree.Measure(size);
tree.Arrange(new Rect(size));
-
-
+
adorned.AttachedToVisualTree += delegate
{
AdornerLayer.SetAdornedElement(adorner, adorned);
AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner);
};
+
tree.Measure(size);
tree.Arrange(new Rect(size));
- await RenderToFile(tree);
- CompareImages(skipImmediate: true);
+ await RenderToFile(tree, testName: testName);
+ CompareImages(skipImmediate: true, testName: testName);
+ }
+
+ [Theory,
+ InlineData(true),
+ InlineData(false)
+ ]
+ public async Task Focus_Adorner_Is_Properly_Clipped(bool clip)
+ {
+ Border adorned;
+ var content = new Border
+ {
+ Background = Brushes.Red,
+ Padding = new Thickness(10, 50, 10, 10),
+ Child = new Border()
+ {
+ Background = Brushes.White,
+ ClipToBounds = true,
+ Padding = new Thickness(0, -30, 0, 0),
+ Child = adorned = new Border
+ {
+ Background = Brushes.Green,
+ VerticalAlignment = VerticalAlignment.Top,
+ Height = 100,
+ Width = 50
+ }
+ }
+ };
+ var adorner = new Border
+ {
+ BorderThickness = new Thickness(2),
+ BorderBrush = Brushes.Black
+ };
+ if (!clip)
+ AdornerLayer.SetIsClipEnabled(adorner, false);
+ await CheckAdornedContent(content, adorned, adorner,
+ testName: "Focus_Adorner_Is_Properly_Clipped_Clip_" + clip);
}
+
}
\ No newline at end of file
diff --git a/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png
new file mode 100644
index 0000000000..4821c22c39
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png
similarity index 100%
rename from tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png
rename to tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png
diff --git a/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png
new file mode 100644
index 0000000000..4821c22c39
Binary files /dev/null and b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png differ
diff --git a/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png
similarity index 100%
rename from tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png
rename to tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png