Skip to content

Conversation

@ThanhNguyxn
Copy link

@ThanhNguyxn ThanhNguyxn commented Dec 6, 2025

Summary

Closes #44111

Replaced the ComboBox dropdown for Quick Accent toolbar position with a visual 3×3 grid, making position selection more intuitive.

PR Checklist

Changes

New Files

  • IndexToBoolConverter.cs - Converter for int-to-bool binding with RadioButton groups

Modified Files

  • PowerAccentPage.xaml - Replaced ComboBox with 3×3 Grid of styled RadioButtons
  • PowerAccentViewModel.cs - Added bounds checking to prevent crash
  • Resources.resw - Added 18 new resource strings for ToolTip and Accessibility

Visual Layout

+----+----+----+
| TL | TC | TR |
+----+----+----+
| L  | C  | R  |
+----+----+----+
| BL | BC | BR |
+----+----+----+

Each tile has:

  • Custom PositionRadioButtonStyle with accent color when selected
  • Localized tooltip on hover (x:Uid with .ToolTip suffix)
  • Localized accessibility name for screen readers (x:Uid with .AutomationProperties.Name suffix)

Accessibility

Using x:Uid binding to resource strings:

  • .ToolTip - Shows position name on hover
  • .[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name - Screen reader support

…grid

Closes microsoft#44111

Replaced the ComboBox dropdown for toolbar position selection with a
visual 3x3 grid of tiles, making it easier to select positions intuitively.

Changes:
- Added IndexToBoolConverter for int-to-bool binding conversion
- Created PositionRadioButtonStyle with accent color highlighting
- Replaced ComboBox with 3x3 Grid of styled RadioButtons
- Each tile shows position with tooltip for accessibility

The grid layout visually represents the 9 toolbar positions:
+----+----+----+
| TL | TC | TR |
+----+----+----+
| L  | C  | R  |
+----+----+----+
| BL | BC | BR |
+----+----+----+
Copilot AI review requested due to automatic review settings December 6, 2025 04:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the ComboBox dropdown for Quick Accent toolbar position selection with an intuitive visual 3×3 grid of RadioButtons, making it easier for users to understand and select where the accent toolbar appears on screen.

Key changes:

  • Created IndexToBoolConverter to enable two-way binding between an integer index and RadioButton selection
  • Replaced dropdown ComboBox with visual 3×3 grid where each position is represented spatially
  • Added custom PositionRadioButtonStyle with accent color highlighting for selected positions

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/settings-ui/Settings.UI/Converters/IndexToBoolConverter.cs New converter for binding integer indices to RadioButton IsChecked state; enables bidirectional sync between ViewModel.ToolbarPositionIndex and 9 RadioButtons
src/settings-ui/Settings.UI/SettingsXAML/Views/PowerAccentPage.xaml Replaces ComboBox with 3×3 grid layout, adds converter resource and custom RadioButton style with visual states

Comment on lines 41 to 42
// Return -1 to indicate no change (RadioButton unchecked)
return -1;
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning -1 when a RadioButton is unchecked could potentially corrupt the ToolbarPositionIndex if the framework calls ConvertBack during automatic RadioButton deselection. Consider returning DependencyProperty.UnsetValue instead to explicitly indicate no update should occur. For reference: using Microsoft.UI.Xaml; and return DependencyProperty.UnsetValue;

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +53
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorSecondaryBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorTertiaryBrush}"/>
</VisualState.Setters>
</VisualState>
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PointerOver and Pressed visual states will override the Checked state's background, making it unclear that the button is selected when hovering or pressing an already-checked button. Consider adding conditions to preserve the accent color when the button is checked. Example: use VisualState.StateTriggers or only change the background for unchecked buttons in hover/pressed states.

Suggested change
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorSecondaryBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorTertiaryBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState.Setters>
<!-- Only apply hover background if not checked -->
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorSecondaryBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOverChecked">
<VisualState.Setters>
<!-- Keep accent color when checked and hovered -->
<Setter Target="RootGrid.Background" Value="{ThemeResource AccentFillColorDefaultBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<!-- Only apply pressed background if not checked -->
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorTertiaryBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PressedChecked">
<VisualState.Setters>
<!-- Keep accent color when checked and pressed -->
<Setter Target="RootGrid.Background" Value="{ThemeResource AccentFillColorDefaultBrush}"/>
</VisualState.Setters>
</VisualState>

Copilot uses AI. Check for mistakes.
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource ControlFillColorTertiaryBrush}"/>
</VisualState.Setters>
</VisualState>
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RadioButton template is missing a Disabled visual state. Since the SettingsGroup can be disabled (via IsEnabled binding), the RadioButtons should visually indicate when they're disabled. Add a VisualState for the Disabled state in the CommonStates group to show reduced opacity or different styling when disabled. Example:

<VisualState x:Name="Disabled">
    <VisualState.Setters>
        <Setter Target="RootGrid.Opacity" Value="0.4"/>
    </VisualState.Setters>
</VisualState>
Suggested change
</VisualState>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="RootGrid.Opacity" Value="0.4"/>
</VisualState.Setters>
</VisualState>

Copilot uses AI. Check for mistakes.
Comment on lines 233 to 274
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=4}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Top right corner"
ToolTipService.ToolTip="Top right corner"/>

<!-- Left (index 2) -->
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=2}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Left"
ToolTipService.ToolTip="Left"/>
<!-- Center (index 8) -->
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=8}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Center"
ToolTipService.ToolTip="Center"/>
<!-- Right (index 3) -->
<RadioButton Grid.Row="1" Grid.Column="2" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=3}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Right"
ToolTipService.ToolTip="Right"/>

<!-- Bottom Left (index 7) -->
<RadioButton Grid.Row="2" Grid.Column="0" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=7}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Bottom left corner"
ToolTipService.ToolTip="Bottom left corner"/>
<!-- Bottom Center (index 1) -->
<RadioButton Grid.Row="2" Grid.Column="1" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=1}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Bottom center"
ToolTipService.ToolTip="Bottom center"/>
<!-- Bottom Right (index 6) -->
<RadioButton Grid.Row="2" Grid.Column="2" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=6}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Bottom right corner"
ToolTipService.ToolTip="Bottom right corner"/>
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tooltip and AutomationProperties should use localized strings via x:Uid instead of hardcoded English text. The existing localization keys from the ComboBoxItems should be reused. For example, use x:Uid="QuickAccent_ToolbarPosition_TopLeftCorner" on the RadioButton instead of hardcoded AutomationProperties.Name and ToolTipService.ToolTip. This ensures the UI is properly localized for all supported languages.

Suggested change
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=4}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Top right corner"
ToolTipService.ToolTip="Top right corner"/>
<!-- Left (index 2) -->
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=2}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Left"
ToolTipService.ToolTip="Left"/>
<!-- Center (index 8) -->
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=8}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Center"
ToolTipService.ToolTip="Center"/>
<!-- Right (index 3) -->
<RadioButton Grid.Row="1" Grid.Column="2" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=3}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Right"
ToolTipService.ToolTip="Right"/>
<!-- Bottom Left (index 7) -->
<RadioButton Grid.Row="2" Grid.Column="0" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=7}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Bottom left corner"
ToolTipService.ToolTip="Bottom left corner"/>
<!-- Bottom Center (index 1) -->
<RadioButton Grid.Row="2" Grid.Column="1" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=1}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Bottom center"
ToolTipService.ToolTip="Bottom center"/>
<!-- Bottom Right (index 6) -->
<RadioButton Grid.Row="2" Grid.Column="2" GroupName="ToolbarPosition"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=6}"
Style="{StaticResource PositionRadioButtonStyle}"
AutomationProperties.Name="Bottom right corner"
ToolTipService.ToolTip="Bottom right corner"/>
x:Uid="QuickAccent_ToolbarPosition_TopRightCorner"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=4}"
Style="{StaticResource PositionRadioButtonStyle}"/>
<!-- Left (index 2) -->
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="ToolbarPosition"
x:Uid="QuickAccent_ToolbarPosition_Left"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=2}"
Style="{StaticResource PositionRadioButtonStyle}"/>
<!-- Center (index 8) -->
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="ToolbarPosition"
x:Uid="QuickAccent_ToolbarPosition_Center"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=8}"
Style="{StaticResource PositionRadioButtonStyle}"/>
<!-- Right (index 3) -->
<RadioButton Grid.Row="1" Grid.Column="2" GroupName="ToolbarPosition"
x:Uid="QuickAccent_ToolbarPosition_Right"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=3}"
Style="{StaticResource PositionRadioButtonStyle}"/>
<!-- Bottom Left (index 7) -->
<RadioButton Grid.Row="2" Grid.Column="0" GroupName="ToolbarPosition"
x:Uid="QuickAccent_ToolbarPosition_BottomLeftCorner"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=7}"
Style="{StaticResource PositionRadioButtonStyle}"/>
<!-- Bottom Center (index 1) -->
<RadioButton Grid.Row="2" Grid.Column="1" GroupName="ToolbarPosition"
x:Uid="QuickAccent_ToolbarPosition_BottomCenter"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=1}"
Style="{StaticResource PositionRadioButtonStyle}"/>
<!-- Bottom Right (index 6) -->
<RadioButton Grid.Row="2" Grid.Column="2" GroupName="ToolbarPosition"
x:Uid="QuickAccent_ToolbarPosition_BottomRightCorner"
IsChecked="{x:Bind Path=ViewModel.ToolbarPositionIndex, Mode=TwoWay, Converter={StaticResource IndexToBoolConverter}, ConverterParameter=6}"
Style="{StaticResource PositionRadioButtonStyle}"/>

Copilot uses AI. Check for mistakes.
/// Converts an integer index to a boolean value for use with RadioButton groups.
/// The ConverterParameter specifies which index value should return true.
/// </summary>
public partial class IndexToBoolConverter : IValueConverter
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider marking this class as sealed for consistency with most other converters in the codebase (e.g., UpdateStateToBoolConverter, ImageResizerUnitToIntConverter). The sealed modifier provides minor performance benefits and prevents unnecessary inheritance.

Suggested change
public partial class IndexToBoolConverter : IValueConverter
public sealed partial class IndexToBoolConverter : IValueConverter

Copilot uses AI. Check for mistakes.
- Add 'sealed' modifier to IndexToBoolConverter class
- Return DependencyProperty.UnsetValue instead of -1 in ConvertBack
- Add Disabled visual state with 0.4 opacity for accessibility
- Use x:Uid for RadioButton localization instead of hardcoded strings
@jiripolasek
Copy link
Collaborator

🟥

  • Doesn’t build
  • Crashes the app (after fixing the build manually)
  • PR description doesn’t match the implementation
  • Accessibility issues
  • UX issues

- Add bounds checking to ToolbarPositionIndex to prevent crash
- Add 18 localized resource strings for .ToolTip and .AutomationProperties.Name
- Use x:Uid in XAML for proper localization support
- Update PR description to match implementation
@ThanhNguyxn
Copy link
Author

Hi @jiripolasek,

Thanks for the detailed review! I've addressed all the issues you mentioned:

Fixes Applied

  1. Build issue - The build should work now with the fixed resource strings
  2. Crash fix - Added bounds validation in ToolbarPositionIndex setter to prevent IndexOutOfRangeException
  3. PR description - Updated to accurately describe the implementation
  4. Accessibility - Added .[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name resource strings for all 9 positions
  5. UX - Added .ToolTip resource strings for hover hints

Changes Summary

  • PowerAccentViewModel.cs - Bounds checking in setter
  • Resources.resw - Added 18 new localized resource strings (ToolTip + AutomationProperties.Name)
  • PowerAccentPage.xaml - Uses x:Uid for proper localization support

Ready for re-review when you have time. Thanks!

@jiripolasek
Copy link
Collaborator

jiripolasek commented Dec 6, 2025

@ThanhNguyxn I think you want to revisit that fix. You've missed a file.

Also, upon fixing the build, it crashes the app.

<data name="QuickAccent_ToolbarPosition_Right.Content" xml:space="preserve">
<value>Right</value>
</data>
<!-- Toolbar position ToolTip and Accessibility strings -->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't put extra changes to a generated file. It would require an effort to maintain this for anyone making changes to the file.

@jiripolasek
Copy link
Collaborator

🟥 This clearly hasn’t been tested, as it can't be built, and crashes the app when the page is loaded, when the build issue is fixed manually.

@jiripolasek jiripolasek added the Product-Quick Accent Refers to the Quick Accent PowerToy label Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Quick Accent Refers to the Quick Accent PowerToy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Easier toolbar position input for Quick Accent

2 participants