Skip to content

Commit

Permalink
Merge pull request #413 from CommunityToolkit/niels9001/settings-cont…
Browse files Browse the repository at this point in the history
…rols-a11y

[SettingsControls] Accessibility improvements
  • Loading branch information
niels9001 authored Apr 13, 2023
2 parents bfc2c90 + 04b1a96 commit 2eaaa48
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="SettingsControlsExperiment.Samples.ClickableSettingsCardSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Expand Down Expand Up @@ -30,5 +30,13 @@
<FontIcon Glyph="&#xE8A7;" />
</labs:SettingsCard.ActionIcon>
</labs:SettingsCard>
<labs:SettingsCard Header="Hiding the ActionIcon"
IsActionIconVisible="False"
IsClickEnabled="True"
IsEnabled="{x:Bind IsCardEnabled, Mode=OneWay}">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE72E;" />
</labs:SettingsCard.HeaderIcon>
</labs:SettingsCard>
</StackPanel>
</Page>
2 changes: 1 addition & 1 deletion components/SettingsControls/samples/SettingsCard.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ You can set the `Header`, `Description`, `HeaderIcon` and `Content` properties t

> [!SAMPLE SettingsCardSample]
SettingsCard can also be turned into a button, by setting the `IsClickEnabled` property. This can be useful whenever you want your settings component to navigate to a detail page or open an external link:
SettingsCard can also be turned into a button, by setting the `IsClickEnabled` property. This can be useful whenever you want your settings component to navigate to a detail page or open an external link. You can set a custom icon by setting the `ActionIcon`, or hiding it completely by setting the `IsActionIconVisible` to `false`.

> [!SAMPLE ClickableSettingsCardSample]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<ToolkitComponentName>SettingsControls</ToolkitComponentName>
<Description>This package contains the SettingsCard and SettingsExpander controls.</Description>
<Version>0.0.17</Version>
<Version>0.0.18</Version>
<LangVersion>10.0</LangVersion>

<!-- Rns suffix is required for namespaces shared across projects. See https://github.com/CommunityToolkit/Labs-Windows/issues/152 -->
Expand Down
9 changes: 9 additions & 0 deletions components/SettingsControls/src/Helpers/ControlHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace CommunityToolkit.Labs.WinUI;
internal static partial class ControlHelpers
{
internal static bool IsXamlRootAvailable { get; } = Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot");
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public partial class SettingsCard : ButtonBase
typeof(SettingsCard),
new PropertyMetadata(defaultValue: false, (d, e) => ((SettingsCard)d).OnIsClickEnabledPropertyChanged((bool)e.OldValue, (bool)e.NewValue)));


/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="ContentAlignment"/> property.
/// </summary>
Expand All @@ -69,6 +68,15 @@ public partial class SettingsCard : ButtonBase
typeof(SettingsCard),
new PropertyMetadata(defaultValue: ContentAlignment.Right));

/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="IsActionIconVisible"/> property.
/// </summary>
public static readonly DependencyProperty IsActionIconVisibleProperty = DependencyProperty.Register(
nameof(IsActionIconVisible),
typeof(bool),
typeof(SettingsCard),
new PropertyMetadata(defaultValue: true, (d, e) => ((SettingsCard)d).OnIsActionIconVisiblePropertyChanged((bool)e.OldValue, (bool)e.NewValue)));

/// <summary>
/// Gets or sets the Header.
/// </summary>
Expand Down Expand Up @@ -134,6 +142,15 @@ public ContentAlignment ContentAlignment
set => SetValue(ContentAlignmentProperty, value);
}

/// <summary>
/// Gets or sets if the ActionIcon is shown.
/// </summary>
public bool IsActionIconVisible
{
get => (bool)GetValue(IsActionIconVisibleProperty);
set => SetValue(IsActionIconVisibleProperty, value);
}

protected virtual void OnIsClickEnabledPropertyChanged(bool oldValue, bool newValue)
{
OnIsClickEnabledChanged();
Expand All @@ -152,6 +169,11 @@ protected virtual void OnDescriptionPropertyChanged(object oldValue, object newV
{
OnDescriptionChanged();
}

protected virtual void OnIsActionIconVisiblePropertyChanged(bool oldValue, bool newValue)
{
OnActionIconChanged();
}
}

public enum ContentAlignment
Expand Down
43 changes: 33 additions & 10 deletions components/SettingsControls/src/SettingsCard/SettingsCard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace CommunityToolkit.Labs.WinUI;
/// A SettingsCard can also be hosted within a SettingsExpander.
/// </summary>

[TemplatePart(Name = ActionIconPresenter, Type = typeof(ContentControl))]
[TemplatePart(Name = ActionIconPresenterHolder, Type = typeof(Viewbox))]
[TemplatePart(Name = HeaderPresenter, Type = typeof(ContentPresenter))]
[TemplatePart(Name = DescriptionPresenter, Type = typeof(ContentPresenter))]
[TemplatePart(Name = HeaderIconPresenterHolder, Type = typeof(Viewbox))]
Expand All @@ -20,7 +20,7 @@ public partial class SettingsCard : ButtonBase
internal const string PressedState = "Pressed";
internal const string DisabledState = "Disabled";

internal const string ActionIconPresenter = "PART_ActionIconPresenter";
internal const string ActionIconPresenterHolder = "PART_ActionIconPresenterHolder";
internal const string HeaderPresenter = "PART_HeaderPresenter";
internal const string DescriptionPresenter = "PART_DescriptionPresenter";
internal const string HeaderIconPresenterHolder = "PART_HeaderIconPresenterHolder";
Expand All @@ -37,7 +37,7 @@ protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
IsEnabledChanged -= OnIsEnabledChanged;
OnButtonIconChanged();
OnActionIconChanged();
OnHeaderChanged();
OnHeaderIconChanged();
OnDescriptionChanged();
Expand All @@ -64,6 +64,7 @@ private void EnableButtonInteraction()
{
DisableButtonInteraction();

IsTabStop = true;
PointerEntered += Control_PointerEntered;
PointerExited += Control_PointerExited;
PointerCaptureLost += Control_PointerCaptureLost;
Expand All @@ -74,6 +75,7 @@ private void EnableButtonInteraction()

private void DisableButtonInteraction()
{
IsTabStop = false;
PointerEntered -= Control_PointerEntered;
PointerExited -= Control_PointerExited;
PointerCaptureLost -= Control_PointerCaptureLost;
Expand All @@ -94,7 +96,11 @@ private void Control_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == Windows.System.VirtualKey.Enter || e.Key == Windows.System.VirtualKey.Space || e.Key == Windows.System.VirtualKey.GamepadA)
{
VisualStateManager.GoToState(this, PressedState, true);
// Check if the active focus is on the card itself - only then we show the pressed state.
if (GetFocusedElement() is SettingsCard)
{
VisualStateManager.GoToState(this, PressedState, true);
}
}
}

Expand Down Expand Up @@ -152,7 +158,7 @@ protected override AutomationPeer OnCreateAutomationPeer()

private void OnIsClickEnabledChanged()
{
OnButtonIconChanged();
OnActionIconChanged();
if (IsClickEnabled)
{
EnableButtonInteraction();
Expand All @@ -168,13 +174,18 @@ private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArg
VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true);
}

private void OnButtonIconChanged()
private void OnActionIconChanged()
{
if (GetTemplateChild(ActionIconPresenter) is FrameworkElement buttonIconPresenter)
if (GetTemplateChild(ActionIconPresenterHolder) is FrameworkElement actionIconPresenter)
{
buttonIconPresenter.Visibility = IsClickEnabled
? Visibility.Visible
: Visibility.Collapsed;
if (IsClickEnabled && IsActionIconVisible)
{
actionIconPresenter.Visibility = Visibility.Visible;
}
else
{
actionIconPresenter.Visibility =Visibility.Collapsed;
}
}
}

Expand Down Expand Up @@ -207,4 +218,16 @@ private void OnHeaderChanged()
: Visibility.Collapsed;
}
}

private FrameworkElement? GetFocusedElement()
{
if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null)
{
return FocusManager.GetFocusedElement(XamlRoot) as FrameworkElement;
}
else
{
return FocusManager.GetFocusedElement() as FrameworkElement;
}
}
}
50 changes: 20 additions & 30 deletions components/SettingsControls/src/SettingsCard/SettingsCard.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,15 @@
</ResourceDictionary.ThemeDictionaries>
<Thickness x:Key="SettingsCardBorderThickness">1</Thickness>
<Thickness x:Key="SettingsCardPadding">16,16,16,16</Thickness>
<Thickness x:Key="SettingsCardIconMargin">2,0,20,0</Thickness>
<x:Double x:Key="SettingsCardMinWidth">148</x:Double>
<x:Double x:Key="SettingsCardMinHeight">68</x:Double>
<x:Double x:Key="SettingsCardActionButtonWidth">32</x:Double>
<x:Double x:Key="SettingsCardActionButtonHeight">32</x:Double>
<x:Double x:Key="SettingsCardDescriptionFontSize">12</x:Double>
<x:Double x:Key="SettingsCardHeaderIconMaxSize">20</x:Double>
<x:Double x:Key="SettingsCardActionIconMaxSize">13</x:Double>
<x:Double x:Key="SettingsCardLeftIndention">0</x:Double>
<x:Double x:Key="SettingsCardContentMinWidth">120</x:Double>
<Thickness x:Key="SettingsCardHeaderIconMargin">2,0,20,0</Thickness>
<Thickness x:Key="SettingsCardActionIconMargin">14,0,0,0</Thickness>
<x:Double x:Key="SettingsCardActionIconMaxSize">13</x:Double>
<Thickness x:Key="SettingsCardVerticalHeaderContentSpacing">0,8,0,0</Thickness>
<x:Double x:Key="SettingsCardWrapThreshold">476</x:Double>
<x:Double x:Key="SettingsCardWrapNoIconThreshold">286</x:Double>
Expand Down Expand Up @@ -385,6 +383,7 @@
MaxHeight="{ThemeResource SettingsCardHeaderIconMaxSize}"
Margin="{ThemeResource SettingsCardHeaderIconMargin}">
<ContentPresenter x:Name="PART_HeaderIconPresenter"
win:AutomationProperties.AccessibilityView="Raw"
win:HighContrastAdjustment="None"
Content="{TemplateBinding HeaderIcon}" />
</Viewbox>
Expand Down Expand Up @@ -439,32 +438,23 @@
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Content="{TemplateBinding Content}" />

<ContentControl x:Name="PART_ActionIconPresenter"
Grid.RowSpan="2"
Grid.Column="3"
Width="{ThemeResource SettingsCardActionButtonWidth}"
Height="{ThemeResource SettingsCardActionButtonHeight}"
Margin="4,0,-8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
win:AutomationProperties.LocalizedControlType="button"
win:AutomationProperties.Name="{TemplateBinding ActionIconToolTip}"
win:HighContrastAdjustment="None"
CornerRadius="{ThemeResource ControlCornerRadius}"
FocusVisualMargin="-3"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
IsTabStop="True"
ToolTipService.ToolTip="{TemplateBinding ActionIconToolTip}"
UseSystemFocusVisuals="True"
Visibility="Collapsed">
<Viewbox MaxWidth="{ThemeResource SettingsCardActionIconMaxSize}"
MaxHeight="{ThemeResource SettingsCardActionIconMaxSize}">
<ContentPresenter win:HighContrastAdjustment="None"
Content="{TemplateBinding ActionIcon}" />
</Viewbox>
</ContentControl>
<Viewbox x:Name="PART_ActionIconPresenterHolder"
Grid.RowSpan="2"
Grid.Column="3"
MaxWidth="{ThemeResource SettingsCardActionIconMaxSize}"
MaxHeight="{ThemeResource SettingsCardActionIconMaxSize}"
Margin="{ThemeResource SettingsCardActionIconMargin}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
win:HighContrastAdjustment="None"
Visibility="Collapsed">
<ContentPresenter x:Name="PART_ActionIconPresenter"
win:AutomationProperties.AccessibilityView="Raw"
win:HighContrastAdjustment="None"
Content="{TemplateBinding ActionIcon}"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="{TemplateBinding ActionIconToolTip}" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
</ResourceDictionary.MergedDictionaries>

<x:String x:Key="SettingsExpanderChevronToolTip">Show all settings</x:String>
<Thickness x:Key="SettingsExpanderHeaderPadding">16,16,0,16</Thickness>
<Thickness x:Key="SettingsExpanderHeaderPadding">16,16,4,16</Thickness>
<Thickness x:Key="SettingsExpanderItemPadding">58,8,44,8</Thickness>
<Thickness x:Key="SettingsExpanderItemBorderThickness">0,1,0,0</Thickness>
<Thickness x:Key="ClickableSettingsExpanderItemPadding">58,8,16,8</Thickness>
<x:Double x:Key="SettingsExpanderContentMinHeight">16</x:Double>
<x:Double x:Key="SettingsExpanderChevronButtonWidth">32</x:Double>
<x:Double x:Key="SettingsExpanderChevronButtonHeight">32</x:Double>

<Style x:Key="DefaultSettingsExpanderItemStyle"
BasedOn="{StaticResource DefaultSettingsCardStyle}"
Expand Down Expand Up @@ -533,9 +535,9 @@

<ContentControl x:Name="ExpandCollapseChevronBorder"
Grid.Column="1"
Width="{StaticResource SettingsCardActionButtonWidth}"
Height="{StaticResource SettingsCardActionButtonHeight}"
Margin="4,0,8,0"
Width="{StaticResource SettingsExpanderChevronButtonWidth}"
Height="{StaticResource SettingsExpanderChevronButtonHeight}"
Margin="0,0,8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public SettingsExpanderAutomationPeer(SettingsExpander owner)
/// <returns>The control type.</returns>
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Button;
return AutomationControlType.Group;
}

/// <summary>
Expand Down

0 comments on commit 2eaaa48

Please sign in to comment.