Browse Source

Merge branch 'master' into fixes/Warnings/Nullable/iOS

pull/10190/head
Max Katz 4 years ago
committed by GitHub
parent
commit
d047e21828
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/IntegrationTestApp/MainWindow.axaml
  2. 10
      src/Avalonia.Base/AvaloniaObject.cs
  3. 4
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  4. 4
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  5. 27
      src/Avalonia.Base/Layout/LayoutInformation.cs
  6. 3
      src/Avalonia.Base/Layout/Layoutable.cs
  7. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  8. 8
      src/Avalonia.Base/StyledElement.cs
  9. 24
      src/Avalonia.Base/Utilities/WeakEvents.cs
  10. 2
      src/Avalonia.Base/Visual.cs
  11. 1
      src/Avalonia.Base/composition-schema.xml
  12. 22
      src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
  13. 5
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  14. 10
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  15. 39
      src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
  16. 6
      src/Avalonia.Controls/Slider.cs
  17. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml
  18. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  19. 3
      src/Avalonia.OpenGL/GlInterface.cs
  20. 1
      src/Avalonia.OpenGL/IGlContext.cs
  21. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
  22. 65
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  23. 35
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
  24. 35
      tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
  25. 81
      tests/Avalonia.RenderTests/Controls/AdornerTests.cs
  26. BIN
      tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png
  27. 0
      tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_True.expected.png
  28. BIN
      tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped_Clip_False.expected.png
  29. 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> </StackPanel>
</Grid> </Grid>
</TabItem> </TabItem>
<TabItem Header="SliderTab">
<Slider VerticalAlignment="Top" Name="Slider" Value="30"/>
</TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</Window> </Window>

10
src/Avalonia.Base/AvaloniaObject.cs

@ -664,14 +664,12 @@ namespace Avalonia
/// <param name="property">The property that has changed.</param> /// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param> /// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param> /// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
protected void RaisePropertyChanged<T>( protected void RaisePropertyChanged<T>(
DirectPropertyBase<T> property, DirectPropertyBase<T> property,
Optional<T> oldValue, T oldValue,
BindingValue<T> newValue, T newValue)
BindingPriority priority = BindingPriority.LocalValue)
{ {
RaisePropertyChanged(property, oldValue, newValue, priority, true); RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue, true);
} }
/// <summary> /// <summary>
@ -720,7 +718,7 @@ namespace Avalonia
/// <returns> /// <returns>
/// True if the value changed, otherwise false. /// True if the value changed, otherwise false.
/// </returns> /// </returns>
protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value) protected bool SetAndRaise<T>(DirectPropertyBase<T> property, ref T field, T value)
{ {
VerifyAccess(); VerifyAccess();

4
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -22,7 +22,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc) if (target is INotifyPropertyChanged inpc)
{ {
WeakEvents.PropertyChanged.Subscribe(inpc, this); WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
} }
ValueChanged(GetValue(target)); ValueChanged(GetValue(target));
@ -39,7 +39,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc) if (target is INotifyPropertyChanged inpc)
{ {
WeakEvents.PropertyChanged.Unsubscribe(inpc, this); WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
} }
} }
} }

4
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -160,7 +160,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged; var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null) if (inpc != null)
WeakEvents.PropertyChanged.Unsubscribe(inpc, this); WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
} }
private object? GetReferenceTarget() private object? GetReferenceTarget()
@ -185,7 +185,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged; var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null) if (inpc != null)
WeakEvents.PropertyChanged.Subscribe(inpc, this); WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
} }
} }
} }

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); } set { SetValue(UseLayoutRoundingProperty, value); }
} }
/// <summary>
/// Gets the available size passed in the previous layout pass, if any.
/// </summary>
internal Size? PreviousMeasure => _previousMeasure; internal Size? PreviousMeasure => _previousMeasure;
/// <summary> /// <summary>

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -48,7 +48,8 @@ namespace Avalonia.Rendering.Composition.Server
{ {
canvas.PostTransform = Matrix.Identity; canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity; canvas.Transform = Matrix.Identity;
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds); if (AdornerIsClipped)
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
} }
var transform = GlobalTransformMatrix; var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform); canvas.PostTransform = MatrixUtils.ToMatrix(transform);
@ -74,7 +75,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopGeometryClip(); canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds) if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip(); canvas.PopClip();
if (AdornedVisual != null) if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip(); canvas.PopClip();
if(Opacity != 1) if(Opacity != 1)
canvas.PopOpacity(); canvas.PopOpacity();

8
src/Avalonia.Base/StyledElement.cs

@ -524,13 +524,7 @@ namespace Avalonia
NotifyResourcesChanged(); NotifyResourcesChanged();
} }
#nullable disable RaisePropertyChanged(ParentProperty, old, Parent);
RaisePropertyChanged(
ParentProperty,
new Optional<StyledElement>(old),
new BindingValue<StyledElement>(Parent),
BindingPriority.LocalValue);
#nullable enable
} }
} }

24
src/Avalonia.Base/Utilities/WeakEvents.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Threading;
namespace Avalonia.Utilities; namespace Avalonia.Utilities;
@ -20,15 +21,30 @@ public class WeakEvents
}); });
/// <summary> /// <summary>
/// Represents PropertyChanged event from <see cref="INotifyPropertyChanged"/> /// Represents PropertyChanged event from <see cref="INotifyPropertyChanged"/> with auto-dispatching to the UI thread
/// </summary> /// </summary>
public static readonly WeakEvent<INotifyPropertyChanged, PropertyChangedEventArgs> public static readonly WeakEvent<INotifyPropertyChanged, PropertyChangedEventArgs>
PropertyChanged = WeakEvent.Register<INotifyPropertyChanged, PropertyChangedEventArgs>( ThreadSafePropertyChanged = WeakEvent.Register<INotifyPropertyChanged, PropertyChangedEventArgs>(
(s, h) => (s, h) =>
{ {
PropertyChangedEventHandler handler = (_, e) => h(s, e); bool unsubscribed = false;
PropertyChangedEventHandler handler = (_, e) =>
{
if (Dispatcher.UIThread.CheckAccess())
h(s, e);
else
Dispatcher.UIThread.Post(() =>
{
if (!unsubscribed)
h(s, e);
});
};
s.PropertyChanged += handler; s.PropertyChanged += handler;
return () => s.PropertyChanged -= handler; return () =>
{
unsubscribed = true;
s.PropertyChanged -= handler;
};
}); });

2
src/Avalonia.Base/Visual.cs

@ -573,7 +573,7 @@ namespace Avalonia
/// <param name="newParent">The new visual parent.</param> /// <param name="newParent">The new visual parent.</param>
protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent) protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent)
{ {
RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue); RaisePropertyChanged(VisualParentProperty, oldParent, newParent);
} }
internal override ParametrizedLogger? GetBindingWarningLogger( internal override ParametrizedLogger? GetBindingWarningLogger(

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="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/> <Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/>
<Property Name="AdornedVisual" Type="CompositionVisual?" Internal="true" /> <Property Name="AdornedVisual" Type="CompositionVisual?" Internal="true" />
<Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" Type="Avalonia.Media.IBrush?" Internal="true" /> <Property Name="OpacityMaskBrush" Type="Avalonia.Media.IBrush?" Internal="true" />
</Object> </Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/> <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) private void UpdateAdornedElement(Visual adorner, Visual? adorned)
{ {
if (adorner.CompositionVisual != null) if (adorner.CompositionVisual != null)
{
adorner.CompositionVisual.AdornedVisual = adorned?.CompositionVisual; adorner.CompositionVisual.AdornedVisual = adorned?.CompositionVisual;
adorner.CompositionVisual.AdornerIsClipped = GetIsClipEnabled(adorner);
}
var info = adorner.GetValue(s_adornedElementInfoProperty); var info = adorner.GetValue(s_adornedElementInfoProperty);
if (info != null) if (info != null)

10
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -345,10 +345,7 @@ namespace Avalonia.Controls.Primitives
if (_oldSelectedItems != SelectedItems) if (_oldSelectedItems != SelectedItems)
{ {
RaisePropertyChanged( RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
SelectedItemsProperty,
new Optional<IList?>(_oldSelectedItems),
new BindingValue<IList?>(SelectedItems));
_oldSelectedItems = SelectedItems; _oldSelectedItems = SelectedItems;
} }
} }
@ -909,10 +906,7 @@ namespace Avalonia.Controls.Primitives
else if (e.PropertyName == nameof(InternalSelectionModel.WritableSelectedItems) && else if (e.PropertyName == nameof(InternalSelectionModel.WritableSelectedItems) &&
_oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems) _oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems)
{ {
RaisePropertyChanged( RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
SelectedItemsProperty,
new Optional<IList?>(_oldSelectedItems),
new BindingValue<IList?>(SelectedItems));
_oldSelectedItems = SelectedItems; _oldSelectedItems = SelectedItems;
} }
else if (e.PropertyName == nameof(ISelectionModel.Source)) else if (e.PropertyName == nameof(ISelectionModel.Source))

39
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@ -1,37 +1,30 @@
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Threading;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public partial class RelativePanel public partial class RelativePanel
{ {
private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
if (d is Layoutable layoutable && layoutable.Parent is Layoutable layoutableParent)
{
layoutableParent.InvalidateArrange();
}
}
static RelativePanel() static RelativePanel()
{ {
ClipToBoundsProperty.OverrideDefaultValue<RelativePanel>(true); ClipToBoundsProperty.OverrideDefaultValue<RelativePanel>(true);
AboveProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AffectsParentArrange<RelativePanel>(
AlignBottomWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignLeftWithPanelProperty, AlignLeftWithProperty, LeftOfProperty,
AlignBottomWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignRightWithPanelProperty, AlignRightWithProperty, RightOfProperty,
AlignHorizontalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignTopWithPanelProperty, AlignTopWithProperty, AboveProperty,
AlignHorizontalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignBottomWithPanelProperty, AlignBottomWithProperty, BelowProperty,
AlignLeftWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignHorizontalCenterWithPanelProperty, AlignHorizontalCenterWithProperty,
AlignLeftWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignVerticalCenterWithPanelProperty, AlignVerticalCenterWithProperty);
AlignRightWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignRightWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AffectsParentMeasure<RelativePanel>(
AlignTopWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignLeftWithPanelProperty, AlignLeftWithProperty, LeftOfProperty,
AlignTopWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignRightWithPanelProperty, AlignRightWithProperty, RightOfProperty,
AlignVerticalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignTopWithPanelProperty, AlignTopWithProperty, AboveProperty,
AlignVerticalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignBottomWithPanelProperty, AlignBottomWithProperty, BelowProperty,
BelowProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignHorizontalCenterWithPanelProperty, AlignHorizontalCenterWithProperty,
LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged); AlignVerticalCenterWithPanelProperty, AlignVerticalCenterWithProperty);
RightOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
} }
/// <summary> /// <summary>

6
src/Avalonia.Controls/Slider.cs

@ -10,6 +10,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Automation; using Avalonia.Automation;
using Avalonia.Controls.Automation.Peers;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -380,6 +381,11 @@ namespace Avalonia.Controls
} }
} }
protected override AutomationPeer OnCreateAutomationPeer()
{
return new SliderAutomationPeer(this);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {

2
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml

@ -16,6 +16,8 @@
<StackPanel Orientation="Horizontal" Spacing="1"> <StackPanel Orientation="Horizontal" Spacing="1">
<Button Margin="0,0,2,0" <Button Margin="0,0,2,0"
Classes="textBoxClearButton" Classes="textBoxClearButton"
Theme="{StaticResource SimpleTextBoxClearButtonTheme}"
Focusable="False"
ToolTip.Tip="Clear" ToolTip.Tip="Clear"
Cursor="Hand" Cursor="Hand"
Command="{Binding $parent[TextBox].Clear}" Command="{Binding $parent[TextBox].Clear}"

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -103,13 +103,13 @@ namespace Avalonia.Diagnostics.ViewModels
public HorizontalAlignment HorizontalAlignment public HorizontalAlignment HorizontalAlignment
{ {
get => _horizontalAlignment; get => _horizontalAlignment;
private set => RaiseAndSetIfChanged(ref _horizontalAlignment, value); set => RaiseAndSetIfChanged(ref _horizontalAlignment, value);
} }
public VerticalAlignment VerticalAlignment public VerticalAlignment VerticalAlignment
{ {
get => _verticalAlignment; get => _verticalAlignment;
private set => RaiseAndSetIfChanged(ref _verticalAlignment, value); set => RaiseAndSetIfChanged(ref _verticalAlignment, value);
} }
public bool HasPadding { get; } public bool HasPadding { get; }

3
src/Avalonia.OpenGL/GlInterface.cs

@ -73,9 +73,6 @@ namespace Avalonia.OpenGL
[GetProcAddress("glFinish")] [GetProcAddress("glFinish")]
public partial void Finish(); public partial void Finish();
[GetProcAddress("glGetIntegerv")]
public partial void GetIntegerv(int name, out int rv);
[GetProcAddress("glGenFramebuffers")] [GetProcAddress("glGenFramebuffers")]
public partial void GenFramebuffers(int count, int* res); public partial void GenFramebuffers(int count, int* res);

1
src/Avalonia.OpenGL/IGlContext.cs

@ -12,7 +12,6 @@ namespace Avalonia.OpenGL
int SampleCount { get; } int SampleCount { get; }
int StencilSize { get; } int StencilSize { get; }
IDisposable MakeCurrent(); IDisposable MakeCurrent();
IDisposable EnsureCurrent();
bool IsSharedWith(IGlContext context); bool IsSharedWith(IGlContext context);
bool CanCreateSharedContext { get; } bool CanCreateSharedContext { get; }
IGlContext? CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null); IGlContext? CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null);

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@ -121,7 +121,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{ {
WeakEvents.PropertyChanged.Unsubscribe(inpc, this); WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
} }
} }
@ -138,7 +138,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
private void SubscribeToChanges() private void SubscribeToChanges()
{ {
if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
WeakEvents.PropertyChanged.Subscribe(inpc, this); WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
} }
} }

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

@ -20,7 +20,6 @@ namespace Avalonia.Win32.Automation
IRawElementProviderSimple, IRawElementProviderSimple,
IRawElementProviderSimple2, IRawElementProviderSimple2,
IRawElementProviderFragment, IRawElementProviderFragment,
IRawElementProviderAdviseEvents,
IInvokeProvider IInvokeProvider
{ {
private static Dictionary<AutomationProperty, UiaPropertyId> s_propertyMap = new() 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 static ConditionalWeakTable<AutomationPeer, AutomationNode> s_nodes = new();
private readonly int[] _runtimeId; private readonly int[] _runtimeId;
private int _raiseFocusChanged;
private int _raisePropertyChanged;
public AutomationNode(AutomationPeer peer) public AutomationNode(AutomationPeer peer)
{ {
_runtimeId = new int[] { 3, GetHashCode() }; _runtimeId = new int[] { 3, GetHashCode() };
Peer = peer; Peer = peer;
s_nodes.Add(peer, this); 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; } public AutomationPeer Peer { get; protected set; }
@ -86,14 +102,6 @@ namespace Avalonia.Win32.Automation
0); 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)] [return: MarshalAs(UnmanagedType.IUnknown)]
public virtual object? GetPatternProvider(int patternId) public virtual object? GetPatternProvider(int patternId)
{ {
@ -188,32 +196,6 @@ namespace Avalonia.Win32.Automation
void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu()); void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu());
void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke()); 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) protected void InvokeSync(Action action)
{ {
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
@ -266,15 +248,6 @@ namespace Avalonia.Win32.Automation
throw new NotSupportedException(); throw new NotSupportedException();
} }
protected void RaiseFocusChanged(AutomationNode? focused)
{
if (_raiseFocusChanged > 0)
{
UiaCoreProviderApi.UiaRaiseAutomationEvent(
focused,
(int)UiaEventId.AutomationFocusChanged);
}
}
private AutomationNode? GetRoot() private AutomationNode? GetRoot()
{ {

35
src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs

@ -10,8 +10,11 @@ namespace Avalonia.Win32.Automation
{ {
[RequiresUnreferencedCode("Requires .NET COM interop")] [RequiresUnreferencedCode("Requires .NET COM interop")]
internal class RootAutomationNode : AutomationNode, internal class RootAutomationNode : AutomationNode,
IRawElementProviderFragmentRoot IRawElementProviderFragmentRoot,
IRawElementProviderAdviseEvents
{ {
private int _raiseFocusChanged;
public RootAutomationNode(AutomationPeer peer) public RootAutomationNode(AutomationPeer peer)
: base(peer) : base(peer)
{ {
@ -42,6 +45,36 @@ namespace Avalonia.Win32.Automation
return GetOrCreate(focus); 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) public void FocusChanged(object? sender, EventArgs e)
{ {
RaiseFocusChanged(GetOrCreate(Peer.GetFocus())); 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 System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -18,56 +19,70 @@ public class AdornerTests : TestBase
{ {
} }
[Fact] async Task CheckAdornedContent(Control content, Control adorned, Control adorner, int width = 200, int height = 200,
public async Task Focus_Adorner_Is_Properly_Clipped() [CallerMemberName] string testName = "")
{ {
Border adorned;
var tree = new Decorator var tree = new Decorator
{ {
Child = new VisualLayerManager Child = new VisualLayerManager
{ {
Child = new Border Child = content
{
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
}
}
}
}, },
Width = 200, Width = width,
Height = 200 Height = height
};
var adorner = new Border
{
BorderThickness = new Thickness(2),
BorderBrush = Brushes.Black
}; };
var size = new Size(tree.Width, tree.Height); var size = new Size(tree.Width, tree.Height);
tree.Measure(size); tree.Measure(size);
tree.Arrange(new Rect(size)); tree.Arrange(new Rect(size));
adorned.AttachedToVisualTree += delegate adorned.AttachedToVisualTree += delegate
{ {
AdornerLayer.SetAdornedElement(adorner, adorned); AdornerLayer.SetAdornedElement(adorner, adorned);
AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner); AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner);
}; };
tree.Measure(size); tree.Measure(size);
tree.Arrange(new Rect(size)); tree.Arrange(new Rect(size));
await RenderToFile(tree); await RenderToFile(tree, testName: testName);
CompareImages(skipImmediate: true); 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