Skip to content

Conversation

@ThanhNguyxn
Copy link

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

  • Added \IndexToBoolConverter\ for int-to-bool binding
  • Created \PositionRadioButtonStyle\ with accent color
  • Replaced ComboBox with 3×3 Grid of styled RadioButtons

Visual Layout

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

Each tile has tooltip for accessibility.

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Easier toolbar position input for Quick Accent

1 participant