Creating a Common.UI.Controls lib (#45542)

## Summary of the Pull Request

@jiripolasek FYI

This PR creates a new `Common.UI.Controls` library that contains shared
WinUI controls. We have been copying code manually between CmdPal and
Settings, and now with the new KBM we will run into the same issue.

This lib has shared controls projects can add to their proj so we have a
single source of truth.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #45388

<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
This commit is contained in:
Niels Laute
2026-02-12 16:45:44 +01:00
committed by GitHub
parent 795c64cc72
commit 75bf64299d
63 changed files with 302 additions and 792 deletions

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.Common.UI.Controls</RootNamespace>
<AssemblyName>PowerToys.Common.UI.Controls</AssemblyName>
<UseWinUI>true</UseWinUI>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<GenerateLibraryLayout>true</GenerateLibraryLayout>
<ProjectPriFileName>PowerToys.Common.UI.Controls.pri</ProjectPriFileName>
<Nullable>enable</Nullable>
<Platforms>x64;ARM64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Common.UI.Controls
{
public partial class CheckBoxWithDescriptionControl : CheckBox
{
public CheckBoxWithDescriptionControl()
{
this.Loaded += CheckBoxSubTextControl_Loaded;
}
protected override void OnApplyTemplate()
{
Update();
base.OnApplyTemplate();
}
private void Update()
{
if (!string.IsNullOrEmpty(Header))
{
AutomationProperties.SetName(this, Header);
}
}
private void CheckBoxSubTextControl_Loaded(object sender, RoutedEventArgs e)
{
StackPanel panel = new StackPanel() { Orientation = Orientation.Vertical };
panel.Children.Add(new TextBlock() { Text = Header, TextWrapping = TextWrapping.WrapWholeWords });
// Add text box only if the description is not empty. Required for additional plugin options.
if (!string.IsNullOrWhiteSpace(Description))
{
panel.Children.Add(new IsEnabledTextBlock() { Style = (Style)Application.Current.Resources["SecondaryIsEnabledTextBlockStyle"], Text = Description });
}
this.Content = panel;
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
nameof(Header),
typeof(string),
typeof(CheckBoxWithDescriptionControl),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
nameof(Description),
typeof(string),
typeof(CheckBoxWithDescriptionControl),
new PropertyMetadata(default(string)));
[Localizable(true)]
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
[Localizable(true)]
public string Description
{
get => (string)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
}
}

View File

@@ -0,0 +1,48 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Common.UI.Controls">
<Style BasedOn="{StaticResource DefaultIsEnabledTextBlockStyle}" TargetType="controls:IsEnabledTextBlock" />
<Style x:Key="DefaultIsEnabledTextBlockStyle" TargetType="controls:IsEnabledTextBlock">
<Setter Property="Foreground" Value="{ThemeResource DefaultTextForegroundThemeBrush}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:IsEnabledTextBlock">
<Grid>
<TextBlock
x:Name="Label"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
IsTextSelectionEnabled="{TemplateBinding IsTextSelectionEnabled}"
Text="{TemplateBinding Text}"
TextWrapping="WrapWholeWords" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Label.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="SecondaryIsEnabledTextBlockStyle"
BasedOn="{StaticResource DefaultIsEnabledTextBlockStyle}"
TargetType="controls:IsEnabledTextBlock">
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
<Setter Property="FontSize" Value="12" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Common.UI.Controls
{
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
public partial class IsEnabledTextBlock : Control
{
public IsEnabledTextBlock()
{
this.DefaultStyleKey = typeof(IsEnabledTextBlock);
}
protected override void OnApplyTemplate()
{
IsEnabledChanged -= IsEnabledTextBlock_IsEnabledChanged;
SetEnabledState();
IsEnabledChanged += IsEnabledTextBlock_IsEnabledChanged;
base.OnApplyTemplate();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(IsEnabledTextBlock), new PropertyMetadata(null));
[Localizable(true)]
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty IsTextSelectionEnabledProperty = DependencyProperty.Register(nameof(IsTextSelectionEnabled), typeof(bool), typeof(IsEnabledTextBlock), new PropertyMetadata(false));
public bool IsTextSelectionEnabled
{
get => (bool)GetValue(IsTextSelectionEnabledProperty);
set => SetValue(IsTextSelectionEnabledProperty, value);
}
private void IsEnabledTextBlock_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetEnabledState();
}
private void SetEnabledState()
{
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
}
}
}

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI">
<Style BasedOn="{StaticResource DefaultKeyCharPresenterStyle}" TargetType="local:KeyCharPresenter" />
<Style x:Key="DefaultKeyCharPresenterStyle" TargetType="local:KeyCharPresenter">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid Height="{TemplateBinding FontSize}">
<TextBlock
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Text="{TemplateBinding Content}"
TextLineBounds="Tight" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="WindowsKeyCharPresenterStyle"
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
TargetType="local:KeyCharPresenter">
<!-- Scale to visually align the height of the Windows logo and text -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid Height="{TemplateBinding FontSize}">
<Viewbox>
<PathIcon Data="M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="OfficeKeyCharPresenterStyle"
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
TargetType="local:KeyCharPresenter">
<!-- Scale to visually align the height of the Office logo and text -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid Height="{TemplateBinding FontSize}">
<Viewbox>
<PathIcon Data="M1792 405v1238q0 33-10 62t-28 54-44 41-57 27l-555 159q-23 6-47 6-31 0-58-8t-53-24l-363-205q-20-11-31-29t-12-42q0-35 24-59t60-25h470V458L735 584q-43 15-69 53t-26 83v651q0 41-20 73t-55 53l-167 91q-23 12-46 12-40 0-68-28t-28-68V587q0-51 26-96t71-71L949 81q41-23 89-23 17 0 30 2t30 8l555 153q31 9 56 27t44 42 29 54 10 61zm-128 1238V405q0-22-13-38t-34-23l-273-75-64-18-64-18v1586l401-115q21-6 34-22t13-39z" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="CopilotKeyCharPresenterStyle"
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
TargetType="local:KeyCharPresenter">
<!-- Scale to visually align the height of the Copilot logo and text -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid Height="{TemplateBinding FontSize}">
<Viewbox>
<PathIcon Data="M0 1213q0-60 10-124t27-130 35-129 38-121q18-55 41-119t54-129 70-125 87-106 106-74 129-28h661q59 0 114 17t96 64q30 34 46 72t33 81l22 58q11 29 34 52 23 25 56 31t65 9h4q157 0 238 83t82 240q0 60-10 125t-27 130-35 128-38 121q-18 55-41 119t-54 129-70 125-87 106-106 74-129 28H790q-61 0-107-15t-82-44-61-72-46-98q-11-29-24-60t-35-55q-23-25-51-31t-60-9h-4q-157 0-238-83T0 1213zm598-957q-50 0-93 25t-79 68-67 94-54 108-42 106-31 91q-17 51-35 110t-33 119-26 121-10 114q0 102 43 149t147 47h163q39 0 74-12t64-35 50-53 34-67q19-58 35-115t35-117q35-117 70-232t72-233q23-73 47-147t63-141H598zm452 285q69-29 143-29h281q-18-29-29-59t-21-58-21-54-30-44-46-30-69-11q-32 0-60 9t-48 35q-17 23-31 53t-27 63-23 65-19 60zm-296 867h101q39 0 74-12t66-34 52-52 33-68l58-191 42-140q21-70 43-140 11-36 28-69t43-62h-101q-39 0-74 12t-66 34-52 52-33 68q-15 48-29 96t-29 96q-21 70-41 140t-44 140q-11 36-28 68t-43 62zm814-768q-39 0-74 12t-64 35-50 53-34 68q-56 174-107 347t-106 349q-23 74-47 147t-63 141h427q50 0 93-25t79-68 67-94 54-108 42-106 31-91q16-51 34-110t34-119 26-121 10-114q0-102-43-149t-147-47h-162zm-570 867q-69 29-143 29H564q17 28 29 58t22 58 24 54 32 45 48 30 71 11q31 0 60-8t49-35q15-19 29-50t28-65 24-69 18-58z" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="GlyphKeyCharPresenterStyle"
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
TargetType="local:KeyCharPresenter">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid>
<Viewbox>
<FontIcon
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Glyph="{TemplateBinding Content}" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Common.UI.Controls;
public sealed partial class KeyCharPresenter : Control
{
public KeyCharPresenter()
{
DefaultStyleKey = typeof(KeyCharPresenter);
}
public object Content
{
get => (object)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyCharPresenter), new PropertyMetadata(default(string)));
}

View File

@@ -0,0 +1,213 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls">
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="local:KeyVisual" />
<Style x:Key="DefaultKeyVisualStyle" TargetType="local:KeyVisual">
<Setter Property="MinWidth" Value="16" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="MinHeight" Value="16" />
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorSecondaryBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="4,4,4,4" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="14" />
<Setter Property="CornerRadius" Value="2" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid
x:Name="KeyHolder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Grid.BackgroundTransition>
<local:KeyCharPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultSolidBrush}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCautionBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCautionBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="SubtleKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="local:KeyVisual">
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid
x:Name="KeyHolder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Grid.BackgroundTransition>
<local:KeyCharPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="AccentKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="local:KeyVisual">
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource AccentControlElevationBorderBrush}" />
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid
x:Name="KeyHolder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Grid.BackgroundTransition>
<local:KeyCharPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource AccentButtonBackgroundDisabled}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource AccentButtonBorderBrushDisabled}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource AccentButtonForegroundDisabled}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCautionBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCautionBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,193 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
namespace Microsoft.PowerToys.Common.UI.Controls
{
[TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))]
[TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
[TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")]
[TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")]
[TemplateVisualState(Name = WarningState, GroupName = "CommonStates")]
public sealed partial class KeyVisual : Control
{
private const string KeyPresenter = "KeyPresenter";
private const string NormalState = "Normal";
private const string DisabledState = "Disabled";
private const string InvalidState = "Invalid";
private const string WarningState = "Warning";
private KeyCharPresenter _keyPresenter = null!;
public object Content
{
get => (object)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
public State State
{
get => (State)GetValue(StateProperty);
set => SetValue(StateProperty, value);
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(State), typeof(KeyVisual), new PropertyMetadata(State.Normal, OnStateChanged));
public bool RenderKeyAsGlyph
{
get => (bool)GetValue(RenderKeyAsGlyphProperty);
set => SetValue(RenderKeyAsGlyphProperty, value);
}
public static readonly DependencyProperty RenderKeyAsGlyphProperty = DependencyProperty.Register(nameof(RenderKeyAsGlyph), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnContentChanged));
public KeyVisual()
{
this.DefaultStyleKey = typeof(KeyVisual);
}
protected override void OnApplyTemplate()
{
IsEnabledChanged -= KeyVisual_IsEnabledChanged;
_keyPresenter = (KeyCharPresenter)this.GetTemplateChild(KeyPresenter);
Update();
SetVisualStates();
IsEnabledChanged += KeyVisual_IsEnabledChanged;
base.OnApplyTemplate();
}
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).SetVisualStates();
}
private static void OnStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).SetVisualStates();
}
private void SetVisualStates()
{
if (this != null)
{
if (State == State.Error)
{
VisualStateManager.GoToState(this, InvalidState, true);
}
else if (State == State.Warning)
{
VisualStateManager.GoToState(this, WarningState, true);
}
else if (!IsEnabled)
{
VisualStateManager.GoToState(this, DisabledState, true);
}
else
{
VisualStateManager.GoToState(this, NormalState, true);
}
}
}
private void Update()
{
if (Content == null)
{
return;
}
if (Content is string key)
{
switch (key)
{
case "Copilot":
_keyPresenter.Style = (Style)Application.Current.Resources["CopilotKeyCharPresenterStyle"];
break;
case "Office":
_keyPresenter.Style = (Style)Application.Current.Resources["OfficeKeyCharPresenterStyle"];
break;
default:
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
break;
}
return;
}
if (Content is int keyCode)
{
VirtualKey virtualKey = (VirtualKey)keyCode;
switch (virtualKey)
{
case VirtualKey.Enter:
SetGlyphOrText("\uE751", virtualKey);
break;
case VirtualKey.Back:
SetGlyphOrText("\uE750", virtualKey);
break;
case VirtualKey.Shift:
case (VirtualKey)160: // Left Shift
case (VirtualKey)161: // Right Shift
SetGlyphOrText("\uE752", virtualKey);
break;
case VirtualKey.Up:
_keyPresenter.Content = "\uE0E4";
break;
case VirtualKey.Down:
_keyPresenter.Content = "\uE0E5";
break;
case VirtualKey.Left:
_keyPresenter.Content = "\uE0E2";
break;
case VirtualKey.Right:
_keyPresenter.Content = "\uE0E3";
break;
case VirtualKey.LeftWindows:
case VirtualKey.RightWindows:
_keyPresenter.Style = (Style)Application.Current.Resources["WindowsKeyCharPresenterStyle"];
break;
}
}
}
private void SetGlyphOrText(string glyph, VirtualKey key)
{
if (RenderKeyAsGlyph)
{
_keyPresenter.Content = glyph;
_keyPresenter.Style = (Style)Application.Current.Resources["GlyphKeyCharPresenterStyle"];
}
else
{
_keyPresenter.Content = key.ToString();
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
}
}
private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetVisualStates();
}
}
public enum State
{
Normal,
Error,
Warning,
}
}

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls"
xmlns:tk="using:CommunityToolkit.WinUI"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls">
<Style BasedOn="{StaticResource DefaultShortcutWithTextLabelControlStyle}" TargetType="local:ShortcutWithTextLabelControl" />
<Style x:Key="DefaultShortcutWithTextLabelControlStyle" TargetType="local:ShortcutWithTextLabelControl">
<Setter Property="KeyVisualStyle" Value="{StaticResource DefaultKeyVisualStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ShortcutWithTextLabelControl">
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsControl
x:Name="ShortcutsControl"
VerticalAlignment="Bottom"
AutomationProperties.AccessibilityView="Raw"
IsTabStop="False"
ItemsSource="{TemplateBinding Keys}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<local:KeyVisual
tk:FrameworkElementExtensions.AncestorType="local:ShortcutWithTextLabelControl"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
IsTabStop="False"
Style="{Binding (tk:FrameworkElementExtensions.Ancestor).KeyVisualStyle, RelativeSource={RelativeSource Self}}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<tkcontrols:MarkdownTextBlock
x:Name="LabelControl"
Grid.Column="1"
VerticalAlignment="Center"
Config="{TemplateBinding MarkdownConfig}"
Text="{TemplateBinding Text}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LabelPlacementStates">
<VisualState x:Name="LabelAfter" />
<VisualState x:Name="LabelBefore">
<VisualState.Setters>
<Setter Target="LabelControl.(Grid.Column)" Value="0" />
<Setter Target="ShortcutsControl.(Grid.Column)" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Common.UI.Controls
{
public sealed partial class ShortcutWithTextLabelControl : Control
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public List<object> Keys
{
get { return (List<object>)GetValue(KeysProperty); }
set { SetValue(KeysProperty, value); }
}
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register(nameof(Keys), typeof(List<object>), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public Placement LabelPlacement
{
get { return (Placement)GetValue(LabelPlacementProperty); }
set { SetValue(LabelPlacementProperty, value); }
}
public static readonly DependencyProperty LabelPlacementProperty = DependencyProperty.Register(nameof(LabelPlacement), typeof(Placement), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(defaultValue: Placement.After, OnIsLabelPlacementChanged));
public MarkdownConfig MarkdownConfig
{
get { return (MarkdownConfig)GetValue(MarkdownConfigProperty); }
set { SetValue(MarkdownConfigProperty, value); }
}
public static readonly DependencyProperty MarkdownConfigProperty = DependencyProperty.Register(nameof(MarkdownConfig), typeof(MarkdownConfig), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(new MarkdownConfig()));
public Style KeyVisualStyle
{
get { return (Style)GetValue(KeyVisualStyleProperty); }
set { SetValue(KeyVisualStyleProperty, value); }
}
public static readonly DependencyProperty KeyVisualStyleProperty = DependencyProperty.Register(nameof(KeyVisualStyle), typeof(Style), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(Style)));
public ShortcutWithTextLabelControl()
{
DefaultStyleKey = typeof(ShortcutWithTextLabelControl);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
private static void OnIsLabelPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs newValue)
{
if (d is ShortcutWithTextLabelControl labelControl)
{
if (labelControl.LabelPlacement == Placement.Before)
{
VisualStateManager.GoToState(labelControl, "LabelBefore", true);
}
else
{
VisualStateManager.GoToState(labelControl, "LabelAfter", true);
}
}
}
public enum Placement
{
Before,
After,
}
}
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Common.UI.Controls
{
public partial class BoolToKeyVisualStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool b && parameter is string param)
{
if (b && param == "Warning")
{
return State.Warning;
}
else if (b && param == "Error")
{
return State.Error;
}
else
{
return State.Normal;
}
}
else
{
return State.Normal;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>