From 32548ec40947949302b7a27936653e5d050b66d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Mon, 13 Apr 2026 16:23:06 -0400 Subject: [PATCH 1/7] Add initial Avalonia accessibility metadata Add AutomationProperties-based semantics to the Avalonia shell, sidebar, package pages, banners, and reusable settings cards. Highlights: - mark decorative icons and chrome out of the accessibility tree - label icon-only controls, search affordances, package lists, and package selection surfaces - expose settings-card content to screen readers and keyboard activation - align implementation with Avalonia 11 supported automation APIs --- .../Views/Controls/InfoBar.axaml | 6 ++ .../Views/Controls/Settings/ButtonCard.cs | 13 ++++- .../Controls/Settings/CheckboxButtonCard.cs | 16 +++++- .../Views/Controls/Settings/CheckboxCard.cs | 10 +++- .../Views/Controls/Settings/ComboboxCard.cs | 7 ++- .../Views/Controls/Settings/SettingsCard.cs | 53 ++++++++++++++++++ .../Controls/Settings/SettingsPageButton.cs | 12 +++- .../Views/Controls/Settings/TextboxCard.cs | 14 ++++- .../Views/Controls/SvgIcon.cs | 6 ++ src/UniGetUI.Avalonia/Views/MainWindow.axaml | 31 +++++++++- src/UniGetUI.Avalonia/Views/SidebarView.axaml | 12 +++- .../SoftwarePages/AbstractPackagesPage.axaml | 56 +++++++++++++++---- 12 files changed, 212 insertions(+), 24 deletions(-) diff --git a/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml b/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml index 31380bb5e2..e8a29085c7 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml +++ b/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml @@ -1,6 +1,7 @@ @@ -8,6 +9,9 @@ @@ -18,6 +22,7 @@ _button.Content = value; + set + { + _button.Content = value; + ApplyAutomationMetadata(_button, GetAutomationNameText(), value); + } } public string Text { - set => Header = value; + set + { + Header = value; + ApplyAutomationMetadata(_button, value); + } } public new event EventHandler? Click; @@ -26,6 +34,7 @@ public ButtonCard() _button.MinWidth = 200; _button.Click += (_, _) => Click?.Invoke(this, EventArgs.Empty); Content = _button; + ApplyAutomationMetadata(_button); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs index b384d88968..dab08cfb17 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Layout; @@ -36,12 +37,21 @@ public CoreSettings.K SettingName public string CheckboxText { - set => _textblock.Text = value; + set + { + _textblock.Text = value; + ApplyAutomationMetadata(_checkbox, value); + ApplyAutomationMetadata(Button, value); + } } public string ButtonText { - set => Button.Content = value; + set + { + Button.Content = value; + ApplyAutomationMetadata(Button, _textblock.Text, value); + } } private bool _buttonAlwaysOn; @@ -71,6 +81,7 @@ public CheckboxButtonCard() FontWeight = FontWeight.Medium, }; IS_INVERTED = false; + AutomationProperties.SetAccessibilityView(Button, AccessibilityView.Control); Content = _checkbox; Header = _textblock; @@ -84,5 +95,6 @@ public CheckboxButtonCard() _textblock.Opacity = (_checkbox.IsChecked ?? false) ? 1 : 0.7; }; Button.Click += (s, e) => Click?.Invoke(s, e); + ApplyAutomationMetadata(_checkbox, _textblock.Text); } } diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxCard.cs index 73af7686df..6080985043 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxCard.cs @@ -1,5 +1,6 @@ using System.Windows.Input; using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Layout; @@ -47,7 +48,11 @@ public CoreSettings.K SettingName public string Text { - set => _textblock.Text = value; + set + { + _textblock.Text = value; + ApplyAutomationMetadata(_checkbox, value, _warningBlock.IsVisible ? _warningBlock.Text : null); + } } public string WarningText @@ -56,6 +61,7 @@ public string WarningText { _warningBlock.Text = value; _warningBlock.IsVisible = value.Any(); + ApplyAutomationMetadata(_checkbox, _textblock.Text, _warningBlock.IsVisible ? value : null); } } @@ -87,6 +93,7 @@ public CheckboxCard() }; _warningBlock.Classes.Add("setting-warning-text"); IS_INVERTED = false; + AutomationProperties.SetAccessibilityView(_warningBlock, AccessibilityView.Raw); Content = _checkbox; Header = new StackPanel @@ -97,6 +104,7 @@ public CheckboxCard() }; _checkbox.IsCheckedChanged += _checkbox_Toggled; + ApplyAutomationMetadata(_checkbox, _textblock.Text); } protected virtual void _checkbox_Toggled(object? sender, RoutedEventArgs e) diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs index 25f545d040..b9fd6fe52b 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs @@ -32,7 +32,11 @@ public CoreSettings.K SettingName public string Text { - set => Header = value; + set + { + Header = value; + ApplyAutomationMetadata(_combobox, value); + } } public event EventHandler? ValueChanged; @@ -42,6 +46,7 @@ public ComboboxCard() _combobox.MinWidth = 200; _combobox.ItemsSource = _elements; Content = _combobox; + ApplyAutomationMetadata(_combobox); } public void AddItem(string name, string value) => AddItem(name, value, true); diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsCard.cs index 8d3d09eedd..4d7e91aac0 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsCard.cs @@ -1,5 +1,7 @@ using System.Windows.Input; using Avalonia; +using Avalonia.Automation; +using Avalonia.Automation.Peers; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -132,6 +134,7 @@ public SettingsCard() Width = 24, Height = 24, }; + AutomationProperties.SetAccessibilityView(_iconPresenter, AccessibilityView.Raw); _headerPresenter = new ContentControl { @@ -208,6 +211,8 @@ public SettingsCard() base.Content = _border; PointerPressed += OnPointerPressed; + KeyDown += OnKeyDown; + SyncAutomationProperties(); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) @@ -224,6 +229,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang VerticalAlignment = VerticalAlignment.Center, } : value; + SyncAutomationProperties(); } else if (change.Property == DescriptionProperty) { @@ -231,6 +237,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang if (value is null) { _descriptionRow.IsVisible = false; + SyncAutomationProperties(); return; } _descriptionPresenter.Content = value is string s @@ -244,15 +251,61 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang } : value; _descriptionRow.IsVisible = true; + SyncAutomationProperties(); } } + protected string? GetAutomationNameText() => ExtractAutomationText(Header); + + protected string? GetAutomationHelpText() => ExtractAutomationText(Description); + + protected void ApplyAutomationMetadata(Control control, string? name = null, string? helpText = null) + { + name ??= GetAutomationNameText(); + helpText ??= GetAutomationHelpText(); + + if (!string.IsNullOrWhiteSpace(name)) + AutomationProperties.SetName(control, name); + + if (!string.IsNullOrWhiteSpace(helpText)) + AutomationProperties.SetHelpText(control, helpText); + } + + private static string? ExtractAutomationText(object? value) => value switch + { + string s when !string.IsNullOrWhiteSpace(s) => s, + TextBlock tb when !string.IsNullOrWhiteSpace(tb.Text) => tb.Text, + SelectableTextBlock stb when !string.IsNullOrWhiteSpace(stb.Text) => stb.Text, + ContentControl cc when cc.Content is not null => ExtractAutomationText(cc.Content), + _ => null, + }; + + private void SyncAutomationProperties() + { + ApplyAutomationMetadata(this); + AutomationProperties.SetControlTypeOverride(this, IsClickEnabled ? AutomationControlType.Button : null); + } + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { if (!_isClickEnabled) return; if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return; + InvokeClick(); + e.Handled = true; + } + + private void OnKeyDown(object? sender, KeyEventArgs e) + { + if (!_isClickEnabled) return; + if (e.Key is not (Key.Enter or Key.Space)) return; + + InvokeClick(); e.Handled = true; + } + + private void InvokeClick() + { Click?.Invoke(this, new RoutedEventArgs()); var cmd = Command; var param = CommandParameter; diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsPageButton.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsPageButton.cs index fa65be12d5..46c6a96a2e 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsPageButton.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/SettingsPageButton.cs @@ -8,12 +8,20 @@ public partial class SettingsPageButton : SettingsCard { public string Text { - set => Header = value; + set + { + Header = value; + ApplyAutomationMetadata(this, value, GetAutomationHelpText()); + } } public string UnderText { - set => Description = value; + set + { + Description = value; + ApplyAutomationMetadata(this, GetAutomationNameText(), value); + } } public IconType Icon diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/TextboxCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/TextboxCard.cs index d4880b833c..a5e1d1991d 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/TextboxCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/TextboxCard.cs @@ -39,12 +39,20 @@ public CoreSettings.K SettingName public string Placeholder { - set => _textbox.Watermark = value; + set + { + _textbox.Watermark = value; + ApplyAutomationMetadata(_textbox, GetAutomationNameText() ?? value); + } } public string Text { - set => Header = value; + set + { + Header = value; + ApplyAutomationMetadata(_textbox, value); + } } public Uri HelpUrl @@ -54,6 +62,7 @@ public Uri HelpUrl _helpUri = value; _helpbutton.IsVisible = true; _helpbutton.Content = CoreTools.Translate("More info"); + ApplyAutomationMetadata(_helpbutton, CoreTools.Translate("More info"), GetAutomationNameText()); } } @@ -79,6 +88,7 @@ public TextboxCard() s.Children.Add(_textbox); Content = s; + ApplyAutomationMetadata(_textbox); } public void SaveValue() diff --git a/src/UniGetUI.Avalonia/Views/Controls/SvgIcon.cs b/src/UniGetUI.Avalonia/Views/Controls/SvgIcon.cs index 001b3bc0d4..0440dc9786 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/SvgIcon.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/SvgIcon.cs @@ -3,6 +3,7 @@ using System.IO; using System.Xml.Linq; using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Platform; @@ -16,6 +17,11 @@ namespace UniGetUI.Avalonia.Views.Controls; /// public class SvgIcon : Control { + public SvgIcon() + { + AutomationProperties.SetAccessibilityView(this, AccessibilityView.Raw); + } + public static readonly StyledProperty PathProperty = AvaloniaProperty.Register(nameof(Path)); diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml index 9d8e2f9eef..9844b5e6d2 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml @@ -1,5 +1,6 @@ @@ -38,12 +40,15 @@ - + @@ -56,6 +61,7 @@ @@ -65,6 +71,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsVisible="{Binding OperationsPanelVisible}" + automation:AutomationProperties.AccessibilityView="Raw" Background="{DynamicResource AppSplitterBackground}" Opacity="0.8"/> @@ -73,6 +80,7 @@ IsVisible="{Binding OperationsPanelVisible}" BorderThickness="0,1,0,0" BorderBrush="{DynamicResource AppBorderBrush}" + automation:AutomationProperties.AccessibilityView="Control" Background="{DynamicResource AppOperationsPanelBackground}"> @@ -108,6 +117,7 @@ @@ -212,6 +228,7 @@ @@ -228,9 +245,11 @@ Background="Transparent" BorderThickness="0" CornerRadius="4" + automation:AutomationProperties.Name="{t:Translate Toggle navigation panel}" ToolTip.Tip="{t:Translate Toggle navigation panel}" Command="{Binding ToggleSidebarCommand}"> @@ -139,13 +147,14 @@ Orientation="Horizontal" Spacing="4" VerticalAlignment="Center"> - + @@ -153,16 +162,19 @@ - - + + - - + + - - + + @@ -184,6 +196,7 @@ Height="36" Padding="8,4" CornerRadius="4" + automation:AutomationProperties.Name="{t:Translate Filters}" IsChecked="{Binding IsFilterPaneOpen, Mode=TwoWay}"> @@ -212,8 +225,10 @@ Width="30" Height="36" Padding="4" + automation:AutomationProperties.Name="{t:Translate More}" CornerRadius="0,4,4,0"> - + @@ -246,6 +261,8 @@ @@ -430,6 +447,9 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding FilteredPackages}" + automation:AutomationProperties.AccessibilityView="Control" + automation:AutomationProperties.Name="{Binding PageTitle}" + automation:AutomationProperties.HelpText="{Binding Subtitle}" AutoGenerateColumns="False" CanUserReorderColumns="True" CanUserResizeColumns="True" @@ -441,6 +461,7 @@ @@ -452,7 +473,9 @@ Width="2*"> - + @@ -520,6 +543,7 @@ @@ -578,10 +605,13 @@ @@ -638,6 +668,7 @@ FontSize="40" Watermark="{t:Translate Search for packages}" Background="{DynamicResource AppWindowBackground}" + automation:AutomationProperties.Name="{t:Translate Search for packages}" Text="{Binding MegaQueryText, Mode=TwoWay}" KeyDown="MegaQueryBlock_KeyDown"/> From 725ba1d907d4ec8562752f501f1a170bd80decc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Mon, 13 Apr 2026 16:24:48 -0400 Subject: [PATCH 2/7] Improve Avalonia dialog accessibility Add dialog-focused accessibility metadata and focus behavior. Highlights: - label form controls in install options with LabeledBy associations - add initial focus for install options, package details, and ignored updates dialogs - expose package details secondary actions and technical fields to screen readers - add ignored-updates row and remove-button semantics --- .../ManageIgnoredUpdatesViewModel.cs | 6 ++ .../DialogPages/InstallOptionsWindow.axaml | 70 +++++++++++++------ .../DialogPages/InstallOptionsWindow.axaml.cs | 7 ++ .../ManageIgnoredUpdatesWindow.axaml | 16 +++++ .../ManageIgnoredUpdatesWindow.axaml.cs | 13 ++++ .../DialogPages/PackageDetailsWindow.axaml | 15 ++++ .../DialogPages/PackageDetailsWindow.axaml.cs | 2 + 7 files changed, 109 insertions(+), 20 deletions(-) diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs index 5a740ddaf2..187600e13b 100644 --- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs @@ -139,6 +139,8 @@ public partial class IgnoredPackageEntryViewModel : ObservableObject public string ManagerIconPath { get; } public string VersionDisplay { get; } public string NewVersion { get; } + public string AutomationName { get; } + public string RemoveAutomationName { get; } private readonly string _ignoredId; @@ -154,6 +156,10 @@ public IgnoredPackageEntryViewModel( ManagerIconPath = managerIconPath; VersionDisplay = versionDisplay; NewVersion = newVersion; + AutomationName = CoreTools.Translate("Package {name} from {manager}") + .Replace("{name}", Name) + .Replace("{manager}", Manager); + RemoveAutomationName = CoreTools.Translate("Remove {0} from ignored updates", Name); } [RelayCommand] diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml index 3b28b01a80..28fc23a8d3 100644 --- a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml +++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml @@ -1,5 +1,6 @@ - - @@ -39,11 +45,13 @@ - @@ -87,12 +95,14 @@ - @@ -117,36 +127,42 @@ - + - + - + diff --git a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/AboutPage.axaml b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/AboutPage.axaml index f9b84bc73d..b82b8719bf 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/AboutPage.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/AboutPage.axaml @@ -1,6 +1,7 @@ + HorizontalAlignment="Center" + automation:AutomationProperties.Name="{t:Translate UniGetUI Homepage}"> + Text="{t:Translate UniGetUI Homepage}" + automation:AutomationProperties.AccessibilityView="Raw"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Contributors.axaml b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Contributors.axaml index 1944116594..a414289923 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Contributors.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Contributors.axaml @@ -1,6 +1,7 @@ + Padding="4,0" + automation:AutomationProperties.Name="{Binding Name}"> + Text="{t:Translate View GitHub Profile}" + automation:AutomationProperties.AccessibilityView="Raw"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/ThirdPartyLicenses.axaml b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/ThirdPartyLicenses.axaml index 8fb4a16ab1..f7613aa8d7 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/ThirdPartyLicenses.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/ThirdPartyLicenses.axaml @@ -1,6 +1,7 @@ + HorizontalAlignment="Center" + automation:AutomationProperties.Name="{Binding License}"> + Text="{Binding License}" + automation:AutomationProperties.AccessibilityView="Raw"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Translators.axaml b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Translators.axaml index b30c3c5158..380492408c 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Translators.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/AboutPages/Translators.axaml @@ -1,6 +1,7 @@ + Margin="0,4,0,8" + automation:AutomationProperties.Name="{t:Translate Become a translator}"> + Text="{t:Translate Become a translator}" + automation:AutomationProperties.AccessibilityView="Raw"/> @@ -61,9 +64,11 @@ Click="GitHubButton_Click" Background="Transparent" BorderThickness="0" - Padding="4,0"> + Padding="4,0" + automation:AutomationProperties.Name="{Binding Name}"> + Text="GitHub" + automation:AutomationProperties.AccessibilityView="Raw"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml b/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml index a2cf70ce5d..0578f663e4 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml @@ -1,6 +1,7 @@ @@ -28,6 +30,7 @@ x:Name="ForwardButton" Width="35" Height="35" Padding="6" CornerRadius="0" + automation:AutomationProperties.Name="{t:Translate Go forward}" Click="ForwardButton_Click"> @@ -36,6 +39,7 @@ @@ -63,7 +70,8 @@ IsIndeterminate="True" IsVisible="False" Height="2" - CornerRadius="0"/> + CornerRadius="0" + automation:AutomationProperties.AccessibilityView="Raw"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/LogPages/BaseLogPage.axaml b/src/UniGetUI.Avalonia/Views/Pages/LogPages/BaseLogPage.axaml index 4ec47e5288..8d58446cf2 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/LogPages/BaseLogPage.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/LogPages/BaseLogPage.axaml @@ -1,6 +1,7 @@ - + SelectedIndex="{Binding SelectedLogLevelIndex}" + automation:AutomationProperties.LabeledBy="{Binding #LogLevelLabel}"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml b/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml index e20ef4c90c..33db9f7280 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml @@ -1,6 +1,7 @@ - + Command="{Binding OpenInBrowserCommand}" + automation:AutomationProperties.Name="{t:Translate View page on browser}"> + @@ -28,7 +31,8 @@ IsIndeterminate="True" IsVisible="False" Height="2" - CornerRadius="0"/> + CornerRadius="0" + automation:AutomationProperties.AccessibilityView="Raw"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Administrator.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Administrator.axaml index 63cb666ccb..8f740e58ed 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Administrator.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Administrator.axaml @@ -1,6 +1,7 @@ + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> @@ -32,7 +34,8 @@ + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> @@ -80,17 +84,20 @@ IsEnabled="{Binding IsLoginButtonEnabled}" Command="{Binding LogoutCommand}" VerticalAlignment="Center" + automation:AutomationProperties.Name="{t:Translate Log out from GitHub}" Content="{t:Translate Log out from GitHub}"/> @@ -130,7 +137,8 @@ + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> @@ -143,7 +159,8 @@ RowSpacing="8" ColumnSpacing="8" Margin="0,8,0,0"> - @@ -151,9 +168,11 @@ Text="{Binding CustomInstall}" IsEnabled="{Binding CliSectionEnabled}" FontFamily="Courier New" - x:Name="CustomInstallBox"/> + x:Name="CustomInstallBox" + automation:AutomationProperties.LabeledBy="{Binding #InstallArgsLabelBlock}"/> - @@ -161,9 +180,11 @@ Text="{Binding CustomUpdate}" IsEnabled="{Binding CliSectionEnabled}" FontFamily="Courier New" - x:Name="CustomUpdateBox"/> + x:Name="CustomUpdateBox" + automation:AutomationProperties.LabeledBy="{Binding #UpdateArgsLabelBlock}"/> - @@ -171,7 +192,8 @@ Text="{Binding CustomUninstall}" IsEnabled="{Binding CliSectionEnabled}" FontFamily="Courier New" - x:Name="CustomUninstallBox"/> + x:Name="CustomUninstallBox" + automation:AutomationProperties.LabeledBy="{Binding #UninstallArgsLabelBlock}"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml index 6cc7e1249b..0781ca8145 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml @@ -1,6 +1,7 @@ + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> @@ -26,7 +28,8 @@ + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> { _isLoadingToggles = true; toggle.IsChecked = manager.IsEnabled(); _isLoadingToggles = false; - ApplyStatusBadge(manager, badge, badgeText); + ApplyStatusBadge(manager, toggle, badge, badgeText); }; toggle.IsCheckedChanged += async (_, _) => { if (_isLoadingToggles) return; CoreSettings.SetDictionaryItem(CoreSettings.K.DisabledManagers, manager.Name, toggle.IsChecked != true); await Task.Run(manager.Initialize); - ApplyStatusBadge(manager, badge, badgeText); + ApplyStatusBadge(manager, toggle, badge, badgeText); AccessibilityAnnouncementService.Announce( CoreTools.Translate("{0} is now {1}", manager.DisplayName, toggle.IsChecked == true ? CoreTools.Translate("Enabled") @@ -118,12 +122,12 @@ public void RefreshToggles() foreach (var (toggle, manager, badge, badgeText) in _rows) { toggle.IsChecked = manager.IsEnabled(); - ApplyStatusBadge(manager, badge, badgeText); + ApplyStatusBadge(manager, toggle, badge, badgeText); } _isLoadingToggles = false; } - private void ApplyStatusBadge(IPackageManager manager, Border badge, TextBlock text) + private void ApplyStatusBadge(IPackageManager manager, ToggleSwitch toggle, Border badge, TextBlock text) { string bgKey, fgKey, label; if (!manager.IsEnabled()) @@ -147,6 +151,9 @@ private void ApplyStatusBadge(IPackageManager manager, Border badge, TextBlock t badge.Background = LookupBrush(bgKey); text.Foreground = LookupBrush(fgKey); text.Text = label; + // Bake state into Name so VoiceOver always announces it on macOS + AutomationProperties.SetName(toggle, $"{manager.DisplayName}, {label}"); + AutomationProperties.SetItemStatus(toggle, label); } private IBrush LookupBrush(string key) diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml index b33890c849..ae8d97bb3c 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml @@ -1,6 +1,7 @@ + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> - + Padding="0" + automation:AutomationProperties.Name="{Binding ShowVersionLabel}"> + + Margin="44,32,4,8" + automation:AutomationProperties.HeadingLevel="2"/> diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs index 128bc4097b..3947d61ca3 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs @@ -1,3 +1,4 @@ +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Input.Platform; using Avalonia.Layout; @@ -88,6 +89,7 @@ private void BuildPage() execGrid.Children.Add(execHint); var execCombo = new ComboBox { HorizontalAlignment = HorizontalAlignment.Stretch }; + AutomationProperties.SetName(execCombo, CoreTools.Translate("Select the executable to be used. The following list shows the executables found by UniGetUI")); foreach (var path in manager.FindCandidateExecutableFiles()) execCombo.Items.Add(path); @@ -159,6 +161,7 @@ private void BuildPage() Background = Brushes.Transparent, BorderThickness = new Thickness(0), }; + AutomationProperties.SetName(copyBtn, CoreTools.Translate("Copy path")); var pathCard = new SettingsCard { BorderThickness = new Thickness(1, 0, 1, 1), @@ -221,6 +224,7 @@ private void BuildPage() ]; var ageCombo = new ComboBox { MinWidth = 200 }; + AutomationProperties.SetName(ageCombo, CoreTools.Translate("Minimum age for updates")); foreach (var (label, _) in ageItems) ageCombo.Items.Add(label); @@ -233,6 +237,7 @@ private void BuildPage() { MinWidth = 200, Watermark = CoreTools.Translate("e.g. 10"), + [AutomationProperties.NameProperty] = CoreTools.Translate("Custom minimum age (days)"), Text = CoreSettings.GetDictionaryItem( CoreSettings.K.PerManagerMinimumUpdateAgeCustom, manager.Name) ?? "", }; @@ -488,12 +493,14 @@ private ButtonCard BuildVcpkgRootCard() IsEnabled = ViewModel.IsCustomVcpkgRootSet, Margin = new Thickness(4, 0), }; + AutomationProperties.SetName(resetBtn, CoreTools.Translate("Reset vcpkg root location")); var openBtn = new Button { Content = CoreTools.Translate("Open"), IsEnabled = ViewModel.IsCustomVcpkgRootSet, Margin = new Thickness(4, 0), }; + AutomationProperties.SetName(openBtn, CoreTools.Translate("Open vcpkg root location")); ViewModel.PropertyChanged += (_, e) => { diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SettingsBasePage.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SettingsBasePage.axaml index edc2eaa130..b61b73d3a0 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SettingsBasePage.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SettingsBasePage.axaml @@ -1,11 +1,13 @@ @@ -19,14 +21,16 @@ Margin="0,0,12,0" Padding="4" Background="Transparent" - BorderThickness="0"> + BorderThickness="0" + automation:AutomationProperties.Name="{t:Translate Go back}"> + VerticalAlignment="Center" + automation:AutomationProperties.HeadingLevel="1"/> @@ -43,6 +47,7 @@ VerticalAlignment="Center" Margin="0,0,16,0"/>