From 7bbbfa414f3407ffc7710230613829bc35dabef2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 27 Mar 2021 00:07:15 +0100 Subject: [PATCH] More OSX accessibility implementation. --- native/Avalonia.Native/src/OSX/automation.mm | 74 ++++++++++++++++++- native/Avalonia.Native/src/OSX/window.mm | 4 + .../Peers/RangeBaseAutomationPeer.cs | 3 + .../Provider/IRangeValueProvider.cs | 12 +++ src/Avalonia.Native/AutomationNode.cs | 15 +++- src/Avalonia.Native/AvnAutomationPeer.cs | 15 +++- src/Avalonia.Native/avn.idl | 23 ++++++ 7 files changed, 142 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 32230ff5b7..b400785a13 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -26,6 +26,17 @@ public: NSAccessibilityPostNotification(_node, NSAccessibilityLayoutChangedNotification); } + virtual void PropertyChanged(AvnAutomationProperty property) override + { + switch (property) { + case RangeValueProvider_Value: + NSAccessibilityPostNotification(_node, NSAccessibilityValueChangedNotification); + break; + default: + break; + } + } + virtual NSObject* GetNSAccessibility() override { return _node; @@ -75,7 +86,7 @@ public: case AutomationStatusBar: return NSAccessibilityTableRole; case AutomationTab: return NSAccessibilityTabGroupRole; case AutomationTabItem: return NSAccessibilityRadioButtonRole; - case AutomationText: return NSAccessibilityTextFieldRole; + case AutomationText: return NSAccessibilityStaticTextRole; case AutomationToolBar: return NSAccessibilityToolbarRole; case AutomationToolTip: return NSAccessibilityPopoverRole; case AutomationTree: return NSAccessibilityOutlineRole; @@ -105,7 +116,39 @@ public: - (NSString *)accessibilityTitle { - return GetNSStringAndRelease(_peer->GetName()); + // StaticText exposes its text via the value property. + if (_peer->GetAutomationControlType() != AutomationText) + { + return GetNSStringAndRelease(_peer->GetName()); + } + + return [super accessibilityTitle]; +} + +- (id)accessibilityValue +{ + if (_peer->IsRangeValueProvider()) + { + return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetValue()]; + } + else if (_peer->IsToggleProvider()) + { + switch (_peer->ToggleProvider_GetToggleState()) { + case 0: return [NSNumber numberWithBool:NO]; + case 1: return [NSNumber numberWithBool:YES]; + default: return [NSNumber numberWithInt:-1]; + } + } + else if (_peer->IsValueProvider()) + { + return GetNSStringAndRelease(_peer->ValueProvider_GetValue()); + } + else if (_peer->GetAutomationControlType() == AutomationText) + { + return GetNSStringAndRelease(_peer->GetName()); + } + + return [super accessibilityValue]; } - (NSArray *)accessibilityChildren @@ -175,16 +218,43 @@ public: - (BOOL)accessibilityPerformPress { + if (!_peer->IsInvokeProvider()) + return NO; _peer->InvokeProvider_Invoke(); return YES; } +- (BOOL)accessibilityPerformIncrement +{ + if (!_peer->IsRangeValueProvider()) + return NO; + auto value = _peer->RangeValueProvider_GetValue(); + value += _peer->RangeValueProvider_GetSmallChange(); + _peer->RangeValueProvider_SetValue(value); + return YES; +} + +- (BOOL)accessibilityPerformDecrement +{ + if (!_peer->IsRangeValueProvider()) + return NO; + auto value = _peer->RangeValueProvider_GetValue(); + value -= _peer->RangeValueProvider_GetSmallChange(); + _peer->RangeValueProvider_SetValue(value); + return YES; +} + - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { if (selector == @selector(accessibilityPerformPress)) { return _peer->IsInvokeProvider(); } + else if (selector == @selector(accessibilityPerformIncrement) || + selector == @selector(accessibilityPerformDecrement)) + { + return _peer->IsRangeValueProvider(); + } return [super isAccessibilitySelectorAllowed:selector]; } diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 456a2db3ee..5d69fc8e75 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -514,6 +514,10 @@ public: NSAccessibilityPostNotification(Window, NSAccessibilityLayoutChangedNotification); } + virtual void PropertyChanged(AvnAutomationProperty property) override + { + } + protected: virtual NSWindowStyleMask GetStyle() { diff --git a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs index bafc4c14fc..31a2b7e7af 100644 --- a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs @@ -19,6 +19,9 @@ namespace Avalonia.Automation.Peers public double Maximum => Owner.Maximum; public double Minimum => Owner.Minimum; public double Value => Owner.Value; + public double SmallChange => Owner.SmallChange; + public double LargeChange => Owner.LargeChange; + public void SetValue(double value) => Owner.Value = value; protected virtual void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs b/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs index d4cd35fcf9..d494e068f7 100644 --- a/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs +++ b/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs @@ -28,6 +28,18 @@ namespace Avalonia.Automation.Provider /// double Value { get; } + /// + /// Gets the value that is added to or subtracted from the Value property when a large + /// change is made, such as with the PAGE DOWN key. + /// + double LargeChange { get; } + + /// + /// Gets the value that is added to or subtracted from the Value property when a small + /// change is made, such as with an arrow key. + /// + double SmallChange { get; } + /// /// Sets the value of the control. /// diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs index c0e46b52b9..2601b2f239 100644 --- a/src/Avalonia.Native/AutomationNode.cs +++ b/src/Avalonia.Native/AutomationNode.cs @@ -22,7 +22,20 @@ namespace Avalonia.Native public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) { - // TODO + AvnAutomationProperty p; + + if (property == AutomationElementIdentifiers.BoundingRectangleProperty) + p = AvnAutomationProperty.AutomationPeer_BoundingRectangle; + else if (property == AutomationElementIdentifiers.ClassNameProperty) + p = AvnAutomationProperty.AutomationPeer_ClassName; + else if (property == AutomationElementIdentifiers.NameProperty) + p = AvnAutomationProperty.AutomationPeer_Name; + else if (property == RangeValuePatternIdentifiers.ValueProperty) + p = AvnAutomationProperty.RangeValueProvider_Value; + else + return; + + Native.PropertyChanged(p); } public void FocusChanged(AutomationPeer? focus) diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index a1519d8d26..0c4ad58b9d 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -75,8 +75,21 @@ namespace Avalonia.Native } public int IsInvokeProvider() => (_inner is IInvokeProvider).AsComBool(); - public void InvokeProvider_Invoke() => ((IInvokeProvider)_inner).Invoke(); + + public int IsRangeValueProvider() => (_inner is IRangeValueProvider).AsComBool(); + public double RangeValueProvider_GetValue() => ((IRangeValueProvider)_inner).Value; + public double RangeValueProvider_GetSmallChange() => ((IRangeValueProvider)_inner).SmallChange; + public double RangeValueProvider_GetLargeChange() => ((IRangeValueProvider)_inner).LargeChange; + public void RangeValueProvider_SetValue(double value) => ((IRangeValueProvider)_inner).SetValue(value); + + public int IsToggleProvider() => (_inner is IToggleProvider).AsComBool(); + public int ToggleProvider_GetToggleState() => (int)((IToggleProvider)_inner).ToggleState; + public void ToggleProvider_Toggle() => ((IToggleProvider)_inner).Toggle(); + + public int IsValueProvider() => (_inner is IValueProvider).AsComBool(); + public IAvnString ValueProvider_GetValue() => ((IValueProvider)_inner).Value.ToAvnString(); + public void ValueProvider_SetValue(string value) => ((IValueProvider)_inner).SetValue(value); public static AvnAutomationPeer? Wrap(AutomationPeer? peer) => peer != null ? new AvnAutomationPeer(peer) : null; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 5697665545..88c9225419 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -218,6 +218,14 @@ enum SystemDecorations { SystemDecorationsFull = 2, } +enum AvnAutomationProperty +{ + AutomationPeer_BoundingRectangle, + AutomationPeer_ClassName, + AutomationPeer_Name, + RangeValueProvider_Value, +} + struct AvnSize { double Width, Height; @@ -808,6 +816,20 @@ interface IAvnAutomationPeer : IUnknown bool IsInvokeProvider(); void InvokeProvider_Invoke(); + + bool IsRangeValueProvider(); + double RangeValueProvider_GetValue(); + double RangeValueProvider_GetSmallChange(); + double RangeValueProvider_GetLargeChange(); + void RangeValueProvider_SetValue(double value); + + bool IsToggleProvider(); + int ToggleProvider_GetToggleState(); + void ToggleProvider_Toggle(); + + bool IsValueProvider(); + IAvnString* ValueProvider_GetValue(); + void ValueProvider_SetValue(char* value); } [uuid(b00af5da-78af-4b33-bfff-4ce13a6239a9)] @@ -821,4 +843,5 @@ interface IAvnAutomationPeerArray : IUnknown interface IAvnAutomationNode : IUnknown { void ChildrenChanged(); + void PropertyChanged(AvnAutomationProperty property); }