From e2fd20c2c41df2a788e09f35500eed9158b7cbb6 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Thu, 5 Oct 2023 16:21:05 +0200 Subject: [PATCH 01/10] Push --- .../samples/SettingsCardSample.xaml | 18 ++++++++- .../src/SettingsCard/SettingsCard.cs | 40 +++++++++++++++++++ .../src/SettingsCard/SettingsCard.xaml | 16 ++++++-- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/components/SettingsControls/samples/SettingsCardSample.xaml b/components/SettingsControls/samples/SettingsCardSample.xaml index bc37bce6..fa7f39d7 100644 --- a/components/SettingsControls/samples/SettingsCardSample.xaml +++ b/components/SettingsControls/samples/SettingsCardSample.xaml @@ -1,4 +1,4 @@ - + + + + + Option 1 + Option 2 + Option 3 + + + + + + + + + + 2,0,20,0 14,0,0,0 13 - 0,8,0,0 + 0,24,0,0 476 286 @@ -294,7 +294,8 @@ - + @@ -376,6 +377,15 @@ + + + + + + + + + Date: Thu, 5 Oct 2023 17:11:28 +0200 Subject: [PATCH 02/10] Code cleanup --- .../samples/SettingsCardSample.xaml | 2 +- .../src/SettingsCard/SettingsCard.cs | 63 ++++++++++++------- .../src/SettingsCard/SettingsCard.xaml | 14 ++--- 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/components/SettingsControls/samples/SettingsCardSample.xaml b/components/SettingsControls/samples/SettingsCardSample.xaml index fa7f39d7..b72edb99 100644 --- a/components/SettingsControls/samples/SettingsCardSample.xaml +++ b/components/SettingsControls/samples/SettingsCardSample.xaml @@ -1,4 +1,4 @@ - + @@ -13,17 +15,46 @@ namespace CommunityToolkit.WinUI.Controls; [TemplatePart(Name = HeaderPresenter, Type = typeof(ContentPresenter))] [TemplatePart(Name = DescriptionPresenter, Type = typeof(ContentPresenter))] [TemplatePart(Name = HeaderIconPresenterHolder, Type = typeof(Viewbox))] + +[TemplateVisualState(Name = NormalState, GroupName = CommonStates)] +[TemplateVisualState(Name = PointerOverState, GroupName = CommonStates)] +[TemplateVisualState(Name = PressedState, GroupName = CommonStates)] +[TemplateVisualState(Name = DisabledState, GroupName = CommonStates)] + +[TemplateVisualState(Name = RightState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = RightWrappedState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = RightWrappedNoIconState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = LeftState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = VerticalState, GroupName = ContentAlignmentStates)] + +[TemplateVisualState(Name = NoContentSpacingState, GroupName = ContentSpacingStates)] +[TemplateVisualState(Name = ContentSpacingState, GroupName = ContentSpacingStates)] + public partial class SettingsCard : ButtonBase { + internal const string CommonStates = "CommonStates"; internal const string NormalState = "Normal"; internal const string PointerOverState = "PointerOver"; internal const string PressedState = "Pressed"; internal const string DisabledState = "Disabled"; + internal const string ContentAlignmentStates = "ContentAlignmentStates"; + internal const string RightState = "Right"; + internal const string RightWrappedState = "RightWrapped"; + internal const string RightWrappedNoIconState = "RightWrappedNoIcon"; + internal const string LeftState = "Left"; + internal const string VerticalState = "Vertical"; + + internal const string ContentSpacingStates = "ContentSpacingStates"; + internal const string NoContentSpacingState = "NoContentSpacing"; + internal const string ContentSpacingState = "ContentSpacing"; + internal const string ActionIconPresenterHolder = "PART_ActionIconPresenterHolder"; internal const string HeaderPresenter = "PART_HeaderPresenter"; internal const string DescriptionPresenter = "PART_DescriptionPresenter"; internal const string HeaderIconPresenterHolder = "PART_HeaderIconPresenterHolder"; + + /// /// Creates a new instance of the class. /// @@ -45,10 +76,14 @@ protected override void OnApplyTemplate() VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); RegisterAutomation(); RegisterPropertyChangedCallback(ContentProperty, OnContentChanged); + + if (GetTemplateChild("ContentAlignmentStates") is VisualStateGroup actionIconPresenter) + { + actionIconPresenter.CurrentStateChanged += this.ContentAlignmentStates_Changed; + } IsEnabledChanged += OnIsEnabledChanged; } - private void RegisterAutomation() { if (Header is string headerString && headerString != string.Empty) @@ -230,34 +265,18 @@ private void OnContentChanged(DependencyObject sender, DependencyProperty dp) private void ContentAlignmentStates_Changed(object sender, VisualStateChangedEventArgs e) { - if (e.NewState.Name == "RightWrapped" || e.NewState.Name == "RightWrappedNoIcon" || e.NewState.Name == "Vertical") + // On state change, checking if the Content should be wrapped (e.g. when the card is made smaller or the ContentAlignment is set to Vertical). If the Content and the Header or Description are not null, we add spacing between the Content and the Header/Description. + if (e.NewState != null && (e.NewState.Name == RightWrappedState || e.NewState.Name == RightWrappedNoIconState || e.NewState.Name == VerticalState) && (Content != null) && (Header != null || Description != null)) { - // Content is wrapped, check if the Header/Description is used and if the Content is empty. Only then add spacing - - if (Content != null) - { - if (Header != null || Description != null) - { - // Set spacing - VisualStateManager.GoToState(this, "ContentSpacingActive", true); - } - else - { - VisualStateManager.GoToState(this, "ContentSpacingNotActive", true); - } - } - else - { - VisualStateManager.GoToState(this, "ContentSpacingNotActive", true); - } + VisualStateManager.GoToState(this, ContentSpacingState, true); } else { - VisualStateManager.GoToState(this, "ContentSpacingNotActive", true); + VisualStateManager.GoToState(this, NoContentSpacingState, true); } } - + private FrameworkElement? GetFocusedElement() { diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml index af63fb68..125fa204 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml @@ -106,7 +106,7 @@ 2,0,20,0 14,0,0,0 13 - 0,24,0,0 + 36 476 286 @@ -294,8 +294,7 @@ - + @@ -311,7 +310,6 @@ - @@ -328,7 +326,6 @@ - @@ -360,7 +357,6 @@ - @@ -379,10 +375,10 @@ - - + + - + From 7746139f8c08ea5b37ca509f7076ecd6b1434ca7 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 6 Oct 2023 16:36:24 +0200 Subject: [PATCH 03/10] Updating samples --- .../samples/ClickableSettingsCardSample.xaml | 7 +++++-- .../samples/SettingsCardSample.xaml | 19 +++---------------- .../SettingsExpanderItemsSourceSample.xaml | 2 +- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/components/SettingsControls/samples/ClickableSettingsCardSample.xaml b/components/SettingsControls/samples/ClickableSettingsCardSample.xaml index 78321fec..cfadad61 100644 --- a/components/SettingsControls/samples/ClickableSettingsCardSample.xaml +++ b/components/SettingsControls/samples/ClickableSettingsCardSample.xaml @@ -1,4 +1,4 @@ - + + IsEnabled="{x:Bind IsCardEnabled, Mode=OneWay}"> + + + - - - Option 1 - Option 2 - Option 3 - - - - - - - - - + + diff --git a/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml b/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml index 75ca3e88..259a7462 100644 --- a/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml +++ b/components/SettingsControls/samples/SettingsExpanderItemsSourceSample.xaml @@ -1,4 +1,4 @@ - + Date: Fri, 6 Oct 2023 17:04:38 +0200 Subject: [PATCH 04/10] Update SettingsCard.xaml --- components/SettingsControls/src/SettingsCard/SettingsCard.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml index 125fa204..d5c1135b 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml @@ -375,7 +375,7 @@ - + From 20c08acf273e95aefc357c74b18513a23e18a818 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 2 Nov 2023 12:30:08 -0500 Subject: [PATCH 05/10] Minor cleanup --- .../SettingsControls/src/SettingsCard/SettingsCard.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.cs b/components/SettingsControls/src/SettingsCard/SettingsCard.cs index 581b4eed..582a16fe 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.cs +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.cs @@ -258,11 +258,6 @@ private void OnHeaderChanged() } - private void OnContentChanged(DependencyObject sender, DependencyProperty dp) - { - - } - private void ContentAlignmentStates_Changed(object sender, VisualStateChangedEventArgs e) { // On state change, checking if the Content should be wrapped (e.g. when the card is made smaller or the ContentAlignment is set to Vertical). If the Content and the Header or Description are not null, we add spacing between the Content and the Header/Description. @@ -276,8 +271,6 @@ private void ContentAlignmentStates_Changed(object sender, VisualStateChangedEve } } - - private FrameworkElement? GetFocusedElement() { if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) From 01462e15b1128a7b2527a9792581c7dfc0fb0d9a Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Mon, 6 Nov 2023 12:15:33 +0100 Subject: [PATCH 06/10] Fix initial render bug --- .../src/SettingsCard/SettingsCard.cs | 25 ++++++++++++++----- .../src/SettingsCard/SettingsCard.xaml | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.cs b/components/SettingsControls/src/SettingsCard/SettingsCard.cs index 582a16fe..ba645331 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.cs +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.cs @@ -73,17 +73,24 @@ protected override void OnApplyTemplate() OnHeaderIconChanged(); OnDescriptionChanged(); OnIsClickEnabledChanged(); - VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); + CheckInitialVisualState(); + RegisterAutomation(); RegisterPropertyChangedCallback(ContentProperty, OnContentChanged); + IsEnabledChanged += OnIsEnabledChanged; + } + + private void CheckInitialVisualState() + { + VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); - if (GetTemplateChild("ContentAlignmentStates") is VisualStateGroup actionIconPresenter) + if (GetTemplateChild("ContentAlignmentStates") is VisualStateGroup contentAlignmentStatesGroup) { - actionIconPresenter.CurrentStateChanged += this.ContentAlignmentStates_Changed; + contentAlignmentStatesGroup.CurrentStateChanged -= this.ContentAlignmentStates_Changed; + CheckVerticalSpacingState(contentAlignmentStatesGroup.CurrentState); + contentAlignmentStatesGroup.CurrentStateChanged += this.ContentAlignmentStates_Changed; } - IsEnabledChanged += OnIsEnabledChanged; } - private void RegisterAutomation() { if (Header is string headerString && headerString != string.Empty) @@ -259,9 +266,15 @@ private void OnHeaderChanged() } private void ContentAlignmentStates_Changed(object sender, VisualStateChangedEventArgs e) + { + CheckVerticalSpacingState(e.NewState); + } + + private void CheckVerticalSpacingState(VisualState s) { // On state change, checking if the Content should be wrapped (e.g. when the card is made smaller or the ContentAlignment is set to Vertical). If the Content and the Header or Description are not null, we add spacing between the Content and the Header/Description. - if (e.NewState != null && (e.NewState.Name == RightWrappedState || e.NewState.Name == RightWrappedNoIconState || e.NewState.Name == VerticalState) && (Content != null) && (Header != null || Description != null)) + + if (s != null && (s.Name == RightWrappedState || s.Name == RightWrappedNoIconState || s.Name == VerticalState) && (Content != null) && (Header != null || Description != null)) { VisualStateManager.GoToState(this, ContentSpacingState, true); } diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml index d5c1135b..d993a5af 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml @@ -106,7 +106,7 @@ 2,0,20,0 14,0,0,0 13 - 36 + 8 476 286 From d052b447c5d818ad52fd4fb3ee3cde85c4360e8c Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Mon, 6 Nov 2023 16:27:31 +0100 Subject: [PATCH 07/10] A11y improvements for SettingsCard --- .../src/SettingsCard/SettingsCard.cs | 7 +- .../SettingsCardAutomationPeer.cs | 79 ++++++++++++------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.cs b/components/SettingsControls/src/SettingsCard/SettingsCard.cs index ba645331..b28c3898 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.cs +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.cs @@ -74,8 +74,8 @@ protected override void OnApplyTemplate() OnDescriptionChanged(); OnIsClickEnabledChanged(); CheckInitialVisualState(); + SetAccessibleContentName(); - RegisterAutomation(); RegisterPropertyChangedCallback(ContentProperty, OnContentChanged); IsEnabledChanged += OnIsEnabledChanged; } @@ -91,11 +91,12 @@ private void CheckInitialVisualState() contentAlignmentStatesGroup.CurrentStateChanged += this.ContentAlignmentStates_Changed; } } - private void RegisterAutomation() + + // We automatically set the AutomationProperties.Name of the Content if not configured. + private void SetAccessibleContentName() { if (Header is string headerString && headerString != string.Empty) { - AutomationProperties.SetName(this, headerString); // We don't want to override an AutomationProperties.Name that is manually set, or if the Content basetype is of type ButtonBase (the ButtonBase.Content will be used then) if (Content is UIElement element && string.IsNullOrEmpty(AutomationProperties.GetName(element)) && element.GetType().BaseType != typeof(ButtonBase) && element.GetType() != typeof(TextBlock)) { diff --git a/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs b/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs index d6f159f3..dc049ca0 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs +++ b/components/SettingsControls/src/SettingsCard/SettingsCardAutomationPeer.cs @@ -2,46 +2,67 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Text; - namespace CommunityToolkit.WinUI.Controls; +/// +/// AutomationPeer for SettingsCard +/// +public class SettingsCardAutomationPeer : FrameworkElementAutomationPeer +{ + /// + /// Initializes a new instance of the class. + /// + /// SettingsCard + public SettingsCardAutomationPeer(SettingsCard owner) + : base(owner) + { + } + /// - /// AutomationPeer for SettingsCard + /// Gets the control type for the element that is associated with the UI Automation peer. /// - public class SettingsCardAutomationPeer : FrameworkElementAutomationPeer + /// The control type. + protected override AutomationControlType GetAutomationControlTypeCore() { - /// - /// Initializes a new instance of the class. - /// - /// SettingsCard - public SettingsCardAutomationPeer(SettingsCard owner) - : base(owner) + if (Owner is SettingsCard settingsCard && settingsCard.IsClickEnabled) { + return AutomationControlType.Button; } - - /// - /// Gets the control type for the element that is associated with the UI Automation peer. - /// - /// The control type. - protected override AutomationControlType GetAutomationControlTypeCore() + else { return AutomationControlType.Group; } + } - /// - /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, - /// differentiates the control represented by this AutomationPeer. - /// - /// The string that contains the name. - protected override string GetClassNameCore() + /// + /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + protected override string GetNameCore() + { + // We only want to announce the button card name if it is clickable, else it's just a regular card that does not receive focus + if (Owner is SettingsCard owner && owner.IsClickEnabled) { - string classNameCore = Owner.GetType().Name; -#if DEBUG_AUTOMATION - System.Diagnostics.Debug.WriteLine("SettingsCardAutomationPeer.GetClassNameCore returns " + classNameCore); -#endif - return classNameCore; + string name = AutomationProperties.GetName(owner); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + else + { + if (owner.Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + return headerString; + } + } } + + return base.GetNameCore(); } +} From ece149f392f1e76a8343d5d68a7076d1d2d5da2d Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 7 Nov 2023 15:38:06 +0100 Subject: [PATCH 08/10] Updates to SettingsExpander narrator announcements --- .../src/SettingsExpander/SettingsExpander.cs | 8 +++--- .../SettingsExpanderAutomationPeer.cs | 27 +++++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/components/SettingsControls/src/SettingsExpander/SettingsExpander.cs b/components/SettingsControls/src/SettingsExpander/SettingsExpander.cs index 02503571..d969a16d 100644 --- a/components/SettingsControls/src/SettingsExpander/SettingsExpander.cs +++ b/components/SettingsControls/src/SettingsExpander/SettingsExpander.cs @@ -25,7 +25,7 @@ public SettingsExpander() protected override void OnApplyTemplate() { base.OnApplyTemplate(); - RegisterAutomation(); + SetAccessibleName(); if (_itemsRepeater != null) { @@ -43,11 +43,11 @@ protected override void OnApplyTemplate() } } - private void RegisterAutomation() + private void SetAccessibleName() { - if (Header is string headerString && headerString != string.Empty) + if (string.IsNullOrEmpty(AutomationProperties.GetName(this))) { - if (!string.IsNullOrEmpty(headerString) && string.IsNullOrEmpty(AutomationProperties.GetName(this))) + if (Header is string headerString && !string.IsNullOrEmpty(headerString)) { AutomationProperties.SetName(this, headerString); } diff --git a/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs b/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs index e4d9fdc7..d6cb35a6 100644 --- a/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs +++ b/components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs @@ -39,11 +39,28 @@ protected override AutomationControlType GetAutomationControlTypeCore() /// The string that contains the name. protected override string GetClassNameCore() { - string classNameCore = Owner.GetType().Name; -#if DEBUG_AUTOMATION - System.Diagnostics.Debug.WriteLine("SettingsCardAutomationPeer.GetClassNameCore returns " + classNameCore); -#endif - return classNameCore; + return Owner.GetType().Name; + } + + protected override string GetNameCore() + { + string name = base.GetNameCore(); + + if (Owner is SettingsExpander owner) + { + if (!string.IsNullOrEmpty(AutomationProperties.GetName(owner))) + { + name = AutomationProperties.GetName(owner); + } + else + { + if (owner.Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + name = headerString; + } + } + } + return name; } /// From 1f5b0b79149d81920b58e8410b5b016ee7b4595c Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Wed, 8 Nov 2023 14:35:23 +0100 Subject: [PATCH 09/10] Update hover color in highcontrast --- components/SettingsControls/src/SettingsCard/SettingsCard.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml index d993a5af..3d2e4dd3 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.xaml +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.xaml @@ -80,7 +80,7 @@ + ResourceKey="SystemColorHighlightColorBrush" /> From cb535cb8c691db56432d8463cef94cac06564f68 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 28 Nov 2023 15:40:52 +0100 Subject: [PATCH 10/10] Adding click event to Clickable card sample --- .../samples/ClickableSettingsCardSample.xaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/SettingsControls/samples/ClickableSettingsCardSample.xaml b/components/SettingsControls/samples/ClickableSettingsCardSample.xaml index cfadad61..3fa46cec 100644 --- a/components/SettingsControls/samples/ClickableSettingsCardSample.xaml +++ b/components/SettingsControls/samples/ClickableSettingsCardSample.xaml @@ -1,4 +1,4 @@ - + -