Browse Source

Merge branch 'master' into fix/RelativePanelIssue

pull/10361/head
Max Katz 3 years ago
committed by GitHub
parent
commit
6de05e1e71
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/IntegrationTestApp/MainWindow.axaml
  2. 27
      src/Avalonia.Base/Layout/LayoutInformation.cs
  3. 3
      src/Avalonia.Base/Layout/Layoutable.cs
  4. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  5. 1
      src/Avalonia.Base/composition-schema.xml
  6. 22
      src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
  7. 5
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  8. 6
      src/Avalonia.Controls/Slider.cs
  9. 65
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  10. 35
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
  11. 35
      tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
  12. 81
      tests/Avalonia.RenderTests/Controls/AdornerTests.cs
  13. BIN
      tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png
  14. 0
      tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png
  15. BIN
      tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png
  16. 0
      tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png

3
samples/IntegrationTestApp/MainWindow.axaml

@ -153,6 +153,9 @@
</StackPanel>
</Grid>
</TabItem>
<TabItem Header="SliderTab">
<Slider VerticalAlignment="Top" Name="Slider" Value="30"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>

27
src/Avalonia.Base/Layout/LayoutInformation.cs

@ -0,0 +1,27 @@
namespace Avalonia.Layout;
/// <summary>
/// Provides access to layout information of a control.
/// </summary>
public static class LayoutInformation
{
/// <summary>
/// Gets the available size constraint passed in the previous layout pass.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>Previous control measure constraint, if any.</returns>
public static Size? GetPreviousMeasureConstraint(Layoutable control)
{
return control.PreviousMeasure;
}
/// <summary>
/// Gets the control bounds used in the previous layout arrange pass.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>Previous control arrange bounds, if any.</returns>
public static Rect? GetPreviousArrangeBounds(Layoutable control)
{
return control.PreviousArrange;
}
}

3
src/Avalonia.Base/Layout/Layoutable.cs

@ -323,6 +323,9 @@ namespace Avalonia.Layout
set { SetValue(UseLayoutRoundingProperty, value); }
}
/// <summary>
/// Gets the available size passed in the previous layout pass, if any.
/// </summary>
internal Size? PreviousMeasure => _previousMeasure;
/// <summary>

5
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();

1
src/Avalonia.Base/composition-schema.xml

@ -26,6 +26,7 @@
<Property Name="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/>
<Property Name="AdornedVisual" Type="CompositionVisual?" Internal="true" />
<Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" Type="Avalonia.Media.IBrush?" Internal="true" />
</Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/>

22
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;
}
}
}

5
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)

6
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);
}
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{

65
src/Windows/Avalonia.Win32/Automation/AutomationNode.cs

@ -20,7 +20,6 @@ namespace Avalonia.Win32.Automation
IRawElementProviderSimple,
IRawElementProviderSimple2,
IRawElementProviderFragment,
IRawElementProviderAdviseEvents,
IInvokeProvider
{
private static Dictionary<AutomationProperty, UiaPropertyId> s_propertyMap = new()
@ -47,14 +46,31 @@ namespace Avalonia.Win32.Automation
private static ConditionalWeakTable<AutomationPeer, AutomationNode> 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()
{

35
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()));

35
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<AppiumWebElement> _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)));
}
}
}

81
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);
}
}

BIN
tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

0
tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png → tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

BIN
tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

0
tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png → tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

Loading…
Cancel
Save