mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 01:36:31 +02:00
[Keyboard Manager] Updated WinUI3 KBM and toggles (#45649)
## Running the Project **Option 1: Test via runner** 1. Check out branch `niels9001/kbm-ux-consolidation` 2. Build PowerToys project 3. Manually build `Modules/KeyboardManagerEditorUI` project separately 4. Run `runner` project 5. Ensure experimental features are enabled in general settings (should be on by default) 6. Launch keyboard manager via settings app **Option 2: Test via installer** 1. Install PowerToys via installer on azure pipeline 1. Launch keyboard manager ## Validation For each page (Text, Remappings, Programs, URLs): * Create shortcuts with variable options and ensure they run as expected * Delete shortcuts and ensure they no longer execute * Try to create invalid shortcuts to check for proper validation * Ensure created shortcuts appear in Power Toys Settings Keyboard manager page * Try toggling shortcuts * Try deleting shortcuts while toggled off ### UI * Any feedback on UI design appreciated as well <img width="1071" height="671" alt="image" src="https://github.com/user-attachments/assets/d2e81de0-6d92-4189-9a33-32e94cce74f7" /> <img width="2142" height="1341" alt="image" src="https://github.com/user-attachments/assets/0e4e5685-fdf1-4dfd-ba52-a2e5bc9a66db" /> Closes: #15870 Closes: #31902 Closes: #45302 Closes: #36227 Closes: #16093 Closes: #13409 Closes: #9919 Closes: #9482 Closes: #8798 Closes: #7054 Closes: #2733 Closes: #2027 Closes: #30167 --------- Co-authored-by: Hao Liu <liuhao3418@gmail.com> Co-authored-by: chenmy77 <162882040+chenmy77@users.noreply.github.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com> Co-authored-by: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com> Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application
|
||||
x:Class="KeyboardManagerEditorUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,48 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls">
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultIconLabelControlStyle}" TargetType="local:IconLabelControl" />
|
||||
|
||||
<Style x:Key="DefaultIconLabelControlStyle" TargetType="local:IconLabelControl">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:IconLabelControl">
|
||||
<Grid
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
ColumnSpacing="4"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<FontIcon
|
||||
x:Name="TypeIcon"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="LabelText"
|
||||
Grid.Column="1"
|
||||
Margin="0,-1,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Text="{TemplateBinding Label}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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 KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
[TemplatePart(Name = TypeIconPart, Type = typeof(FontIcon))]
|
||||
[TemplatePart(Name = LabelTextPart, Type = typeof(TextBlock))]
|
||||
public sealed partial class IconLabelControl : Control
|
||||
{
|
||||
private const string TypeIconPart = "TypeIcon";
|
||||
private const string LabelTextPart = "LabelText";
|
||||
|
||||
private FontIcon? _typeIcon;
|
||||
private TextBlock? _labelText;
|
||||
|
||||
public static readonly DependencyProperty ActionTypeProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ActionType),
|
||||
typeof(ActionType),
|
||||
typeof(IconLabelControl),
|
||||
new PropertyMetadata(ActionType.Text, OnActionTypeChanged));
|
||||
|
||||
public static readonly DependencyProperty LabelProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Label),
|
||||
typeof(string),
|
||||
typeof(IconLabelControl),
|
||||
new PropertyMetadata(string.Empty));
|
||||
|
||||
public ActionType ActionType
|
||||
{
|
||||
get => (ActionType)GetValue(ActionTypeProperty);
|
||||
set => SetValue(ActionTypeProperty, value);
|
||||
}
|
||||
|
||||
public string Label
|
||||
{
|
||||
get => (string)GetValue(LabelProperty);
|
||||
set => SetValue(LabelProperty, value);
|
||||
}
|
||||
|
||||
public IconLabelControl()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(IconLabelControl);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_typeIcon = GetTemplateChild(TypeIconPart) as FontIcon;
|
||||
_labelText = GetTemplateChild(LabelTextPart) as TextBlock;
|
||||
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
private static void OnActionTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is IconLabelControl control)
|
||||
{
|
||||
control.UpdateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIcon()
|
||||
{
|
||||
if (_typeIcon == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_typeIcon.Glyph = ActionType switch
|
||||
{
|
||||
ActionType.Program => "\uECAA",
|
||||
ActionType.Text => "\uE8D2",
|
||||
ActionType.Shortcut => "\uEDA7",
|
||||
ActionType.MouseClick => "\uE962",
|
||||
ActionType.Url => "\uE774",
|
||||
_ => "\uE8A5",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Controls.UnifiedMappingControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:commoncontrols="using:Microsoft.PowerToys.Common.UI.Controls"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Loaded="UserControl_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
Width="720"
|
||||
MinHeight="480"
|
||||
ColumnSpacing="24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left Column: Original Input (Trigger) -->
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Orientation="Vertical"
|
||||
Spacing="8">
|
||||
<TextBlock x:Uid="TriggerLabel" FontWeight="SemiBold" />
|
||||
<ComboBox
|
||||
x:Name="TriggerTypeComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionChanged="TriggerTypeComboBox_SelectionChanged">
|
||||
<ComboBoxItem
|
||||
x:Uid="TriggerType_KeyOrShortcut"
|
||||
IsSelected="True"
|
||||
Tag="KeyOrShortcut">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="TriggerType_KeyOrShortcut_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
<!--
|
||||
<ComboBoxItem x:Uid="TriggerType_Mouse" Tag="Mouse">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="TriggerType_Mouse_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
-->
|
||||
</ComboBox>
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,12,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
|
||||
<tkcontrols:SwitchPresenter
|
||||
x:Name="TriggerSwitchPresenter"
|
||||
TargetType="x:String"
|
||||
Value="{Binding SelectedItem.Tag, ElementName=TriggerTypeComboBox}">
|
||||
|
||||
<!-- Key or Shortcut Trigger -->
|
||||
<tkcontrols:Case Value="KeyOrShortcut">
|
||||
<StackPanel Orientation="Vertical" Spacing="8">
|
||||
<ToggleButton
|
||||
x:Name="TriggerKeyToggleBtn"
|
||||
MinHeight="86"
|
||||
Padding="8,24,8,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Checked="TriggerKeyToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}"
|
||||
Unchecked="TriggerKeyToggleBtn_Unchecked">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="TriggerKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel
|
||||
HorizontalSpacing="4"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual
|
||||
Padding="8"
|
||||
Background="{ThemeResource ControlFillColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
Content="{Binding}"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||
FontSize="16"
|
||||
Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<CheckBox
|
||||
x:Name="AllowChordsCheckBox"
|
||||
x:Uid="AllowChordsCheckBox"
|
||||
Click="AllowChordsCheckBox_Click"
|
||||
IsChecked="True" />
|
||||
</StackPanel>
|
||||
</tkcontrols:Case>
|
||||
<!-- Mouse Button Trigger -->
|
||||
<tkcontrols:Case Value="Mouse">
|
||||
<ComboBox
|
||||
x:Name="MouseTriggerComboBox"
|
||||
MinHeight="86"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center">
|
||||
<ComboBoxItem x:Uid="MouseButton_Left" Tag="LeftMouse" />
|
||||
<ComboBoxItem x:Uid="MouseButton_Center" Tag="CenterMouse" />
|
||||
<ComboBoxItem x:Uid="MouseButton_Right" Tag="RightMouse" />
|
||||
<ComboBoxItem x:Uid="MouseButton_Button1" Tag="Button1" />
|
||||
<ComboBoxItem x:Uid="MouseButton_Button2" Tag="Button2" />
|
||||
</ComboBox>
|
||||
</tkcontrols:Case>
|
||||
</tkcontrols:SwitchPresenter>
|
||||
<CheckBox
|
||||
x:Name="AppSpecificCheckBox"
|
||||
x:Uid="AppSpecificCheckBox"
|
||||
IsEnabled="False" />
|
||||
<TextBox
|
||||
x:Name="AppNameTextBox"
|
||||
x:Uid="AppNameTextBox"
|
||||
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||
GotFocus="AppNameTextBox_GotFocus"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
<!-- Arrow Separator -->
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
RowSpacing="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
Width="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{ThemeResource TextFillColorDisabledBrush}" />
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
Width="12"
|
||||
Height="12">
|
||||
<PathIcon Data="{StaticResource ArrowIconData}" Foreground="{ThemeResource TextFillColorDisabledBrush}" />
|
||||
</Viewbox>
|
||||
<Rectangle
|
||||
Grid.Row="2"
|
||||
Width="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{ThemeResource TextFillColorDisabledBrush}" />
|
||||
</Grid>
|
||||
<!-- Right Column: Action Output -->
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Orientation="Vertical"
|
||||
Spacing="8">
|
||||
<TextBlock x:Uid="ActionLabel" FontWeight="SemiBold" />
|
||||
<ComboBox
|
||||
x:Name="ActionTypeComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionChanged="ActionTypeComboBox_SelectionChanged">
|
||||
<ComboBoxItem
|
||||
x:Uid="ActionType_KeyOrShortcut"
|
||||
IsSelected="True"
|
||||
Tag="KeyOrShortcut">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="ActionType_KeyOrShortcut_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem x:Uid="ActionType_Text" Tag="Text">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="ActionType_Text_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem x:Uid="ActionType_OpenUrl" Tag="OpenUrl">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="ActionType_OpenUrl_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem x:Uid="ActionType_OpenApp" Tag="OpenApp">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="ActionType_OpenApp_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
<!--
|
||||
<ComboBoxItem x:Uid="ActionType_MouseClick" Tag="MouseClick">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="ActionType_MouseClick_Text" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
-->
|
||||
</ComboBox>
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,12,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<tkcontrols:SwitchPresenter
|
||||
x:Name="ActionSwitchPresenter"
|
||||
TargetType="x:String"
|
||||
Value="{Binding SelectedItem.Tag, ElementName=ActionTypeComboBox}">
|
||||
<!-- Key or Shortcut Action -->
|
||||
<tkcontrols:Case Value="KeyOrShortcut">
|
||||
<ToggleButton
|
||||
x:Name="ActionKeyToggleBtn"
|
||||
MinHeight="86"
|
||||
Padding="8,24,8,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Checked="ActionKeyToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}"
|
||||
Unchecked="ActionKeyToggleBtn_Unchecked">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="ActionKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel
|
||||
HorizontalSpacing="4"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual
|
||||
Padding="8"
|
||||
Background="{ThemeResource CustomAccentBackgroundBrush}"
|
||||
BorderThickness="0"
|
||||
Content="{Binding}"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</tkcontrols:Case>
|
||||
<!-- Text Action -->
|
||||
<tkcontrols:Case Value="Text">
|
||||
<TextBox
|
||||
x:Name="TextContentBox"
|
||||
x:Uid="TextContentBox"
|
||||
MinHeight="120"
|
||||
MaxHeight="240"
|
||||
AcceptsReturn="True"
|
||||
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||
GotFocus="TextContentBox_GotFocus"
|
||||
TextChanged="TextContentBox_TextChanged"
|
||||
TextWrapping="Wrap" />
|
||||
</tkcontrols:Case>
|
||||
<!-- Open URL Action -->
|
||||
<tkcontrols:Case Value="OpenUrl">
|
||||
<TextBox
|
||||
x:Name="UrlPathInput"
|
||||
x:Uid="UrlPathInput"
|
||||
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||
GotFocus="UrlPathInput_GotFocus"
|
||||
TextChanged="UrlPathInput_TextChanged" />
|
||||
</tkcontrols:Case>
|
||||
<!-- Open App Action -->
|
||||
<tkcontrols:Case Value="OpenApp">
|
||||
<StackPanel Orientation="Vertical" Spacing="16">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
x:Name="ProgramPathInput"
|
||||
x:Uid="ProgramPathInput"
|
||||
HorizontalAlignment="Stretch"
|
||||
GotFocus="ProgramPathInput_GotFocus"
|
||||
TextChanged="ProgramPathInput_TextChanged" />
|
||||
<Button
|
||||
x:Name="ProgramPathSelectButton"
|
||||
x:Uid="ProgramPathSelectButton"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Click="ProgramPathSelectButton_Click"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Style="{StaticResource SubtleButtonStyle}" />
|
||||
</Grid>
|
||||
<TextBox
|
||||
x:Name="ProgramArgsInput"
|
||||
x:Uid="ProgramArgsInput"
|
||||
GotFocus="ProgramArgsInput_GotFocus" />
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
x:Name="StartInPathInput"
|
||||
x:Uid="StartInPathInput"
|
||||
HorizontalAlignment="Stretch"
|
||||
GotFocus="StartInPathInput_GotFocus" />
|
||||
<Button
|
||||
x:Name="StartInSelectButton"
|
||||
x:Uid="StartInSelectButton"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Click="StartInSelectButton_Click"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Style="{StaticResource SubtleButtonStyle}" />
|
||||
</Grid>
|
||||
<ComboBox
|
||||
x:Name="ElevationComboBox"
|
||||
x:Uid="ElevationComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedIndex="0">
|
||||
<ComboBoxItem x:Uid="Elevation_Normal" />
|
||||
<ComboBoxItem x:Uid="Elevation_Elevated" />
|
||||
<ComboBoxItem x:Uid="Elevation_DifferentUser" />
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
x:Name="IfRunningComboBox"
|
||||
x:Uid="IfRunningComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedIndex="0">
|
||||
<ComboBoxItem x:Uid="IfRunning_ShowWindow" />
|
||||
<ComboBoxItem x:Uid="IfRunning_StartAnother" />
|
||||
<ComboBoxItem x:Uid="IfRunning_DoNothing" />
|
||||
<ComboBoxItem x:Uid="IfRunning_Close" />
|
||||
<ComboBoxItem x:Uid="IfRunning_EndTask" />
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
x:Name="VisibilityComboBox"
|
||||
x:Uid="VisibilityComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedIndex="0">
|
||||
<ComboBoxItem x:Uid="Visibility_Normal" />
|
||||
<ComboBoxItem x:Uid="Visibility_Hidden" />
|
||||
<ComboBoxItem x:Uid="Visibility_Minimized" />
|
||||
<ComboBoxItem x:Uid="Visibility_Maximized" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</tkcontrols:Case>
|
||||
<!-- Mouse Click Action (Placeholder) -->
|
||||
<tkcontrols:Case Value="MouseClick">
|
||||
<TextBlock
|
||||
x:Uid="MouseClickPlaceholder"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap" />
|
||||
</tkcontrols:Case>
|
||||
</tkcontrols:SwitchPresenter>
|
||||
</StackPanel>
|
||||
<!-- Validation InfoBar spanning all columns -->
|
||||
<InfoBar
|
||||
x:Name="ValidationInfoBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0,16,0,0"
|
||||
IsClosable="False"
|
||||
IsOpen="False"
|
||||
Severity="Warning" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,997 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
|
||||
|
||||
#pragma warning disable SA1124 // Do not use regions
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Unified control that consolidates all mapping input types:
|
||||
/// - Key/Shortcut remapping (InputControl)
|
||||
/// - Text output (TextPageInputControl)
|
||||
/// - URL opening (UrlPageInputControl)
|
||||
/// - App launching (AppPageInputControl)
|
||||
/// </summary>
|
||||
public sealed partial class UnifiedMappingControl : UserControl, IDisposable, IKeyboardHookTarget
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly ObservableCollection<string> _triggerKeys = new();
|
||||
private readonly ObservableCollection<string> _actionKeys = new();
|
||||
|
||||
private bool _disposed;
|
||||
private bool _internalUpdate;
|
||||
|
||||
private KeyInputMode _currentInputMode = KeyInputMode.OriginalKeys;
|
||||
|
||||
// Dirty tracking: marks fields that have had content then were cleared
|
||||
private bool _textContentDirty;
|
||||
private bool _urlPathDirty;
|
||||
private bool _programPathDirty;
|
||||
|
||||
public bool AllowChords { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever the validation state of the control changes (inputs filled/cleared).
|
||||
/// </summary>
|
||||
public event EventHandler? ValidationStateChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the type of trigger for the mapping.
|
||||
/// </summary>
|
||||
public enum TriggerType
|
||||
{
|
||||
KeyOrShortcut,
|
||||
Mouse,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the type of action to perform.
|
||||
/// </summary>
|
||||
public enum ActionType
|
||||
{
|
||||
KeyOrShortcut,
|
||||
Text,
|
||||
OpenUrl,
|
||||
OpenApp,
|
||||
MouseClick,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the mouse button options.
|
||||
/// </summary>
|
||||
public enum MouseButton
|
||||
{
|
||||
LeftMouse,
|
||||
RightMouse,
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current trigger type.
|
||||
/// </summary>
|
||||
public TriggerType CurrentTriggerType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TriggerTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||
{
|
||||
return item.Tag?.ToString() switch
|
||||
{
|
||||
"Mouse" => TriggerType.Mouse,
|
||||
_ => TriggerType.KeyOrShortcut,
|
||||
};
|
||||
}
|
||||
|
||||
return TriggerType.KeyOrShortcut;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current action type.
|
||||
/// </summary>
|
||||
public ActionType CurrentActionType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ActionTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||
{
|
||||
return item.Tag?.ToString() switch
|
||||
{
|
||||
"Text" => ActionType.Text,
|
||||
"OpenUrl" => ActionType.OpenUrl,
|
||||
"OpenApp" => ActionType.OpenApp,
|
||||
"MouseClick" => ActionType.MouseClick,
|
||||
_ => ActionType.KeyOrShortcut,
|
||||
};
|
||||
}
|
||||
|
||||
return ActionType.KeyOrShortcut;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public UnifiedMappingControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
TriggerKeys.ItemsSource = _triggerKeys;
|
||||
ActionKeys.ItemsSource = _actionKeys;
|
||||
|
||||
_triggerKeys.CollectionChanged += (_, _) => RaiseValidationStateChanged();
|
||||
_actionKeys.CollectionChanged += (_, _) => RaiseValidationStateChanged();
|
||||
|
||||
this.Unloaded += UnifiedMappingControl_Unloaded;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle Events
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Set up event handlers for app-specific checkbox
|
||||
AppSpecificCheckBox.Checked += AppSpecificCheckBox_Changed;
|
||||
AppSpecificCheckBox.Unchecked += AppSpecificCheckBox_Changed;
|
||||
|
||||
// Activate keyboard hook for the trigger input
|
||||
if (TriggerKeyToggleBtn.IsChecked == true)
|
||||
{
|
||||
_currentInputMode = KeyInputMode.OriginalKeys;
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnifiedMappingControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Reset();
|
||||
CleanupKeyboardHook();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Trigger Type Handling
|
||||
|
||||
private void TriggerTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (TriggerTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||
{
|
||||
string? tag = item.Tag?.ToString();
|
||||
|
||||
// Cleanup keyboard hook when switching to mouse
|
||||
if (tag == "Mouse")
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (TriggerKeyToggleBtn.IsChecked == true)
|
||||
{
|
||||
_currentInputMode = KeyInputMode.OriginalKeys;
|
||||
|
||||
// Uncheck action toggle if checked
|
||||
if (ActionKeyToggleBtn?.IsChecked == true)
|
||||
{
|
||||
ActionKeyToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerKeyToggleBtn_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_currentInputMode == KeyInputMode.OriginalKeys)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Action Type Handling
|
||||
|
||||
private void ActionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ActionTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||
{
|
||||
string? tag = item.Tag?.ToString();
|
||||
|
||||
// Cleanup keyboard hook when switching away from key/shortcut
|
||||
if (tag != "KeyOrShortcut")
|
||||
{
|
||||
if (_currentInputMode == KeyInputMode.RemappedKeys)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
}
|
||||
|
||||
if (ActionKeyToggleBtn?.IsChecked == true)
|
||||
{
|
||||
ActionKeyToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HideValidationMessage();
|
||||
RaiseValidationStateChanged();
|
||||
}
|
||||
|
||||
private void ActionKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ActionKeyToggleBtn.IsChecked == true)
|
||||
{
|
||||
_currentInputMode = KeyInputMode.RemappedKeys;
|
||||
|
||||
// Uncheck trigger toggle if checked
|
||||
if (TriggerKeyToggleBtn?.IsChecked == true)
|
||||
{
|
||||
TriggerKeyToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void ActionKeyToggleBtn_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_currentInputMode == KeyInputMode.RemappedKeys)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region App-Specific Handling
|
||||
|
||||
private void AppSpecificCheckBox_Changed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_internalUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
|
||||
AppNameTextBox.Visibility = AppSpecificCheckBox.IsChecked == true
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateAppSpecificCheckBoxState()
|
||||
{
|
||||
// Only enable app-specific remapping for shortcuts (multiple keys).
|
||||
bool isShortcut = _triggerKeys.Count > 1;
|
||||
bool alreadyChecked = AppSpecificCheckBox.IsChecked == true;
|
||||
|
||||
try
|
||||
{
|
||||
_internalUpdate = true;
|
||||
|
||||
AppSpecificCheckBox.IsEnabled = isShortcut || alreadyChecked;
|
||||
if (!isShortcut && !alreadyChecked)
|
||||
{
|
||||
AppSpecificCheckBox.IsChecked = false;
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_internalUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TextBox Focus Handlers
|
||||
|
||||
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
private void TextContentBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
private void TextContentBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
_textContentDirty = true;
|
||||
RaiseValidationStateChanged();
|
||||
}
|
||||
|
||||
private void UrlPathInput_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
private void UrlPathInput_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
_urlPathDirty = true;
|
||||
RaiseValidationStateChanged();
|
||||
}
|
||||
|
||||
private void ProgramPathInput_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
private void ProgramPathInput_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
_programPathDirty = true;
|
||||
RaiseValidationStateChanged();
|
||||
}
|
||||
|
||||
private void ProgramArgsInput_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
private void StartInPathInput_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File/Folder Pickers
|
||||
|
||||
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new FileOpenPicker();
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
|
||||
StorageFile file = await picker.PickSingleFileAsync();
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
ProgramPathInput.Text = file.Path;
|
||||
RaiseValidationStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartInSelectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new FolderPicker();
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
picker.FileTypeFilter.Add("*");
|
||||
|
||||
StorageFolder folder = await picker.PickSingleFolderAsync();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
StartInPathInput.Text = folder.Path;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IKeyboardHookTarget Implementation
|
||||
|
||||
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
if (_currentInputMode == KeyInputMode.OriginalKeys)
|
||||
{
|
||||
_triggerKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_triggerKeys.Add(keyName);
|
||||
}
|
||||
|
||||
UpdateAppSpecificCheckBoxState();
|
||||
}
|
||||
else if (_currentInputMode == KeyInputMode.RemappedKeys)
|
||||
{
|
||||
_actionKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_actionKeys.Add(keyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearKeys()
|
||||
{
|
||||
if (_currentInputMode == KeyInputMode.OriginalKeys)
|
||||
{
|
||||
_triggerKeys.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_actionKeys.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInputLimitReached()
|
||||
{
|
||||
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API - Getters
|
||||
|
||||
/// <summary>
|
||||
/// Gets the trigger keys.
|
||||
/// </summary>
|
||||
public List<string> GetTriggerKeys() => _triggerKeys.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the action keys (for Key/Shortcut action type).
|
||||
/// </summary>
|
||||
public List<string> GetActionKeys() => _actionKeys.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected mouse trigger.
|
||||
/// </summary>
|
||||
public MouseButton? GetMouseTrigger()
|
||||
{
|
||||
if (MouseTriggerComboBox?.SelectedItem is ComboBoxItem item)
|
||||
{
|
||||
return item.Tag?.ToString() switch
|
||||
{
|
||||
"LeftMouse" => MouseButton.LeftMouse,
|
||||
"RightMouse" => MouseButton.RightMouse,
|
||||
"ScrollUp" => MouseButton.ScrollUp,
|
||||
"ScrollDown" => MouseButton.ScrollDown,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text content (for Text action type).
|
||||
/// </summary>
|
||||
public string GetTextContent() => TextContentBox?.Text ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL (for OpenUrl action type).
|
||||
/// </summary>
|
||||
public string GetUrl() => UrlPathInput?.Text ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the program path (for OpenApp action type).
|
||||
/// </summary>
|
||||
public string GetProgramPath() => ProgramPathInput?.Text ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the program arguments (for OpenApp action type).
|
||||
/// </summary>
|
||||
public string GetProgramArgs() => ProgramArgsInput?.Text ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start-in directory (for OpenApp action type).
|
||||
/// </summary>
|
||||
public string GetStartInDirectory() => StartInPathInput?.Text ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the mapping is app-specific.
|
||||
/// </summary>
|
||||
public bool GetIsAppSpecific()
|
||||
{
|
||||
return AppSpecificCheckBox?.IsChecked ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the app name for app-specific mappings.
|
||||
/// </summary>
|
||||
public string GetAppName()
|
||||
{
|
||||
return GetIsAppSpecific() ? (AppNameTextBox?.Text ?? string.Empty) : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elevation level (for OpenApp action type).
|
||||
/// </summary>
|
||||
public ElevationLevel GetElevationLevel() => (ElevationLevel)(ElevationComboBox?.SelectedIndex ?? 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the window visibility (for OpenApp action type).
|
||||
/// </summary>
|
||||
public StartWindowType GetVisibility() => (StartWindowType)(VisibilityComboBox?.SelectedIndex ?? 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the if-running action (for OpenApp action type).
|
||||
/// </summary>
|
||||
public ProgramAlreadyRunningAction GetIfRunningAction() => (ProgramAlreadyRunningAction)(IfRunningComboBox?.SelectedIndex ?? 0);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API - Validation
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when all required fields for the current action type are filled.
|
||||
/// </summary>
|
||||
public bool IsInputComplete()
|
||||
{
|
||||
// Trigger keys are always required
|
||||
if (_triggerKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CurrentActionType switch
|
||||
{
|
||||
ActionType.KeyOrShortcut => _actionKeys.Count > 0,
|
||||
ActionType.Text => !string.IsNullOrWhiteSpace(TextContentBox?.Text),
|
||||
ActionType.OpenUrl => !string.IsNullOrWhiteSpace(UrlPathInput?.Text),
|
||||
ActionType.OpenApp => !string.IsNullOrWhiteSpace(ProgramPathInput?.Text),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API - Setters
|
||||
|
||||
/// <summary>
|
||||
/// Sets the trigger keys.
|
||||
/// </summary>
|
||||
public void SetTriggerKeys(List<string> keys)
|
||||
{
|
||||
_triggerKeys.Clear();
|
||||
if (keys != null)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_triggerKeys.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAppSpecificCheckBoxState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the action keys.
|
||||
/// </summary>
|
||||
public void SetActionKeys(List<string> keys)
|
||||
{
|
||||
_actionKeys.Clear();
|
||||
if (keys != null)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_actionKeys.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the action type.
|
||||
/// </summary>
|
||||
public void SetActionType(ActionType actionType)
|
||||
{
|
||||
int index = actionType switch
|
||||
{
|
||||
ActionType.Text => 1,
|
||||
ActionType.OpenUrl => 2,
|
||||
ActionType.OpenApp => 3,
|
||||
ActionType.MouseClick => 4,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if (ActionTypeComboBox != null)
|
||||
{
|
||||
ActionTypeComboBox.SelectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text content (for Text action type).
|
||||
/// </summary>
|
||||
public void SetTextContent(string text)
|
||||
{
|
||||
if (TextContentBox != null)
|
||||
{
|
||||
TextContentBox.Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the URL (for OpenUrl action type).
|
||||
/// </summary>
|
||||
public void SetUrl(string url)
|
||||
{
|
||||
if (UrlPathInput != null)
|
||||
{
|
||||
UrlPathInput.Text = url;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the program path (for OpenApp action type).
|
||||
/// </summary>
|
||||
public void SetProgramPath(string path)
|
||||
{
|
||||
if (ProgramPathInput != null)
|
||||
{
|
||||
ProgramPathInput.Text = path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the program arguments (for OpenApp action type).
|
||||
/// </summary>
|
||||
public void SetProgramArgs(string args)
|
||||
{
|
||||
if (ProgramArgsInput != null)
|
||||
{
|
||||
ProgramArgsInput.Text = args;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start-in directory (for OpenApp action type).
|
||||
/// </summary>
|
||||
public void SetStartInDirectory(string path)
|
||||
{
|
||||
if (StartInPathInput != null)
|
||||
{
|
||||
StartInPathInput.Text = path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the elevation level (for OpenApp action type).
|
||||
/// </summary>
|
||||
public void SetElevationLevel(ElevationLevel elevationLevel)
|
||||
{
|
||||
if (ElevationComboBox != null)
|
||||
{
|
||||
ElevationComboBox.SelectedIndex = (int)elevationLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the window visibility (for OpenApp action type).
|
||||
/// </summary>
|
||||
public void SetVisibility(StartWindowType visibility)
|
||||
{
|
||||
if (VisibilityComboBox != null)
|
||||
{
|
||||
VisibilityComboBox.SelectedIndex = (int)visibility;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the if-already-running action (for OpenApp action type).
|
||||
/// </summary>
|
||||
public void SetIfRunningAction(ProgramAlreadyRunningAction ifRunningAction)
|
||||
{
|
||||
if (IfRunningComboBox != null)
|
||||
{
|
||||
IfRunningComboBox.SelectedIndex = (int)ifRunningAction;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the mapping is app-specific.
|
||||
/// </summary>
|
||||
public void SetAppSpecific(bool isAppSpecific, string appName)
|
||||
{
|
||||
if (AppSpecificCheckBox != null)
|
||||
{
|
||||
AppSpecificCheckBox.IsChecked = isAppSpecific;
|
||||
if (isAppSpecific && AppNameTextBox != null)
|
||||
{
|
||||
AppNameTextBox.Text = appName;
|
||||
AppNameTextBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private void UncheckAllToggleButtons()
|
||||
{
|
||||
if (TriggerKeyToggleBtn?.IsChecked == true)
|
||||
{
|
||||
TriggerKeyToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
if (ActionKeyToggleBtn?.IsChecked == true)
|
||||
{
|
||||
ActionKeyToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupKeyboardHook()
|
||||
{
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
|
||||
private void RaiseValidationStateChanged()
|
||||
{
|
||||
UpdateInlineValidation();
|
||||
ValidationStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows or hides the inline validation InfoBar based on the current state.
|
||||
/// Only shows errors for output fields that have been interacted with (had content then cleared).
|
||||
/// </summary>
|
||||
private void UpdateInlineValidation()
|
||||
{
|
||||
// Only validate the active action type's output field
|
||||
switch (CurrentActionType)
|
||||
{
|
||||
case ActionType.Text:
|
||||
if (TextContentBox != null && _textContentDirty && string.IsNullOrWhiteSpace(TextContentBox.Text))
|
||||
{
|
||||
ShowValidationErrorFromType(ValidationErrorType.EmptyTargetText);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ActionType.OpenUrl:
|
||||
if (UrlPathInput != null && _urlPathDirty && string.IsNullOrWhiteSpace(UrlPathInput.Text))
|
||||
{
|
||||
ShowValidationErrorFromType(ValidationErrorType.EmptyUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ActionType.OpenApp:
|
||||
if (ProgramPathInput != null && _programPathDirty && string.IsNullOrWhiteSpace(ProgramPathInput.Text))
|
||||
{
|
||||
ShowValidationErrorFromType(ValidationErrorType.EmptyProgramPath);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
HideValidationMessage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all inputs to their default state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_triggerKeys.Clear();
|
||||
_actionKeys.Clear();
|
||||
|
||||
UncheckAllToggleButtons();
|
||||
|
||||
_currentInputMode = KeyInputMode.OriginalKeys;
|
||||
|
||||
// Reset dirty tracking
|
||||
_textContentDirty = false;
|
||||
_urlPathDirty = false;
|
||||
_programPathDirty = false;
|
||||
|
||||
// Hide any validation messages
|
||||
HideValidationMessage();
|
||||
|
||||
// Reset combo boxes
|
||||
if (TriggerTypeComboBox != null)
|
||||
{
|
||||
TriggerTypeComboBox.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
if (ActionTypeComboBox != null)
|
||||
{
|
||||
ActionTypeComboBox.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
if (MouseTriggerComboBox != null)
|
||||
{
|
||||
MouseTriggerComboBox.SelectedIndex = -1;
|
||||
}
|
||||
|
||||
// Reset text inputs
|
||||
if (TextContentBox != null)
|
||||
{
|
||||
TextContentBox.Text = string.Empty;
|
||||
}
|
||||
|
||||
if (UrlPathInput != null)
|
||||
{
|
||||
UrlPathInput.Text = string.Empty;
|
||||
}
|
||||
|
||||
if (ProgramPathInput != null)
|
||||
{
|
||||
ProgramPathInput.Text = string.Empty;
|
||||
}
|
||||
|
||||
if (ProgramArgsInput != null)
|
||||
{
|
||||
ProgramArgsInput.Text = string.Empty;
|
||||
}
|
||||
|
||||
if (StartInPathInput != null)
|
||||
{
|
||||
StartInPathInput.Text = string.Empty;
|
||||
}
|
||||
|
||||
if (AppNameTextBox != null)
|
||||
{
|
||||
AppNameTextBox.Text = string.Empty;
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// Reset checkboxes
|
||||
if (AppSpecificCheckBox != null)
|
||||
{
|
||||
AppSpecificCheckBox.IsChecked = false;
|
||||
AppSpecificCheckBox.IsEnabled = false;
|
||||
}
|
||||
|
||||
// Reset app combo boxes
|
||||
if (ElevationComboBox != null)
|
||||
{
|
||||
ElevationComboBox.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
if (IfRunningComboBox != null)
|
||||
{
|
||||
IfRunningComboBox.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
if (VisibilityComboBox != null)
|
||||
{
|
||||
VisibilityComboBox.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
HideValidationMessage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets only the toggle buttons without clearing the key displays.
|
||||
/// </summary>
|
||||
public void ResetToggleButtons()
|
||||
{
|
||||
UncheckAllToggleButtons();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notifications
|
||||
|
||||
/// <summary>
|
||||
/// Shows a warning notification in the InfoBar.
|
||||
/// </summary>
|
||||
public void ShowNotificationTip(string message)
|
||||
{
|
||||
ShowValidationMessage("Warning", message, InfoBarSeverity.Warning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows an error in the InfoBar with title and message.
|
||||
/// </summary>
|
||||
public void ShowValidationError(string title, string message)
|
||||
{
|
||||
ShowValidationMessage(title, message, InfoBarSeverity.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a validation error based on the error type.
|
||||
/// </summary>
|
||||
public void ShowValidationErrorFromType(ValidationErrorType errorType)
|
||||
{
|
||||
if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out var messageInfo))
|
||||
{
|
||||
ShowValidationError(messageInfo.Title, messageInfo.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowValidationError("Validation Error", "An unknown validation error occurred.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a message in the InfoBar with the specified severity.
|
||||
/// </summary>
|
||||
private void ShowValidationMessage(string title, string message, InfoBarSeverity severity)
|
||||
{
|
||||
if (ValidationInfoBar != null)
|
||||
{
|
||||
ValidationInfoBar.Title = title;
|
||||
ValidationInfoBar.Message = message;
|
||||
ValidationInfoBar.Severity = severity;
|
||||
ValidationInfoBar.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the validation InfoBar.
|
||||
/// </summary>
|
||||
public void HideValidationMessage()
|
||||
{
|
||||
if (ValidationInfoBar != null)
|
||||
{
|
||||
ValidationInfoBar.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
HideValidationMessage();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void AllowChordsCheckBox_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AllowChords = AllowChordsCheckBox.IsChecked == true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore SA1124 // Do not use regions
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public enum ActionType
|
||||
{
|
||||
Program,
|
||||
Text,
|
||||
Shortcut,
|
||||
MouseClick,
|
||||
Url,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public static class EditorConstants
|
||||
{
|
||||
// Default notification timeout
|
||||
public const int DefaultNotificationTimeout = 1500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
internal interface IToggleableShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; }
|
||||
|
||||
bool IsActive { get; set; }
|
||||
|
||||
string Id { get; set; }
|
||||
|
||||
string AppName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public enum KeyInputMode
|
||||
{
|
||||
OriginalKeys,
|
||||
RemappedKeys,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class KeyboardHookHelper : IDisposable
|
||||
{
|
||||
private static KeyboardHookHelper? _instance;
|
||||
|
||||
public static KeyboardHookHelper Instance => _instance ??= new KeyboardHookHelper();
|
||||
|
||||
private KeyboardMappingService _mappingService;
|
||||
|
||||
private HotkeySettingsControlHook? _keyboardHook;
|
||||
|
||||
// The active page using this keyboard hook
|
||||
private IKeyboardHookTarget? _activeTarget;
|
||||
|
||||
private HashSet<VirtualKey> _currentlyPressedKeys = new();
|
||||
private List<VirtualKey> _keyPressOrder = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
// Singleton to make sure only one instance of the hook is active
|
||||
private KeyboardHookHelper()
|
||||
{
|
||||
_mappingService = new KeyboardMappingService();
|
||||
}
|
||||
|
||||
public void ActivateHook(IKeyboardHookTarget target)
|
||||
{
|
||||
CleanupHook();
|
||||
|
||||
_activeTarget = target;
|
||||
|
||||
_currentlyPressedKeys.Clear();
|
||||
_keyPressOrder.Clear();
|
||||
|
||||
_keyboardHook = new HotkeySettingsControlHook(
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
() => true,
|
||||
(key, extraInfo) => true);
|
||||
}
|
||||
|
||||
public void CleanupHook()
|
||||
{
|
||||
if (_keyboardHook != null)
|
||||
{
|
||||
_keyboardHook.Dispose();
|
||||
_keyboardHook = null;
|
||||
}
|
||||
|
||||
_currentlyPressedKeys.Clear();
|
||||
_keyPressOrder.Clear();
|
||||
_activeTarget = null;
|
||||
}
|
||||
|
||||
private void KeyDown(int key)
|
||||
{
|
||||
if (_activeTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualKey virtualKey = (VirtualKey)key;
|
||||
|
||||
if (_currentlyPressedKeys.Contains(virtualKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if no keys are pressed, clear the lists when a new key is pressed
|
||||
if (_currentlyPressedKeys.Count == 0)
|
||||
{
|
||||
_activeTarget.ClearKeys();
|
||||
_keyPressOrder.Clear();
|
||||
}
|
||||
|
||||
// Count current modifiers
|
||||
int modifierCount = _currentlyPressedKeys.Count(k => RemappingHelper.IsModifierKey(k));
|
||||
|
||||
// If adding this key would exceed the limits (4 modifiers + 1 action key), don't add it and show notification
|
||||
if ((RemappingHelper.IsModifierKey(virtualKey) && modifierCount >= 4) ||
|
||||
(!RemappingHelper.IsModifierKey(virtualKey) && _currentlyPressedKeys.Count >= 5))
|
||||
{
|
||||
_activeTarget.OnInputLimitReached();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a different variant of a modifier key already pressed
|
||||
if (RemappingHelper.IsModifierKey(virtualKey))
|
||||
{
|
||||
// Remove existing variant of this modifier key if a new one is pressed
|
||||
// This is to ensure that only one variant of a modifier key is displayed at a time
|
||||
RemoveExistingModifierVariant(virtualKey);
|
||||
}
|
||||
|
||||
if (_currentlyPressedKeys.Add(virtualKey))
|
||||
{
|
||||
_keyPressOrder.Add(virtualKey);
|
||||
|
||||
// Notify the target page
|
||||
_activeTarget.OnKeyDown(virtualKey, GetFormattedKeyList());
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyUp(int key)
|
||||
{
|
||||
if (_activeTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualKey virtualKey = (VirtualKey)key;
|
||||
|
||||
if (_currentlyPressedKeys.Remove(virtualKey))
|
||||
{
|
||||
_keyPressOrder.Remove(virtualKey);
|
||||
|
||||
_activeTarget.OnKeyUp(virtualKey, GetFormattedKeyList());
|
||||
}
|
||||
}
|
||||
|
||||
// Display the modifier keys and the action key in order, e.g. "Ctrl + Alt + A"
|
||||
private List<string> GetFormattedKeyList()
|
||||
{
|
||||
if (_activeTarget == null)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
List<string> keyList = new List<string>();
|
||||
List<VirtualKey> modifierKeys = new List<VirtualKey>();
|
||||
VirtualKey? actionKey = null;
|
||||
VirtualKey? actionKeyChord = null;
|
||||
|
||||
foreach (var key in _keyPressOrder)
|
||||
{
|
||||
if (!_currentlyPressedKeys.Contains(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RemappingHelper.IsModifierKey(key))
|
||||
{
|
||||
if (!modifierKeys.Contains(key))
|
||||
{
|
||||
modifierKeys.Add(key);
|
||||
}
|
||||
}
|
||||
else if (actionKey.HasValue && _activeTarget.AllowChords)
|
||||
{
|
||||
actionKeyChord = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
actionKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in modifierKeys)
|
||||
{
|
||||
keyList.Add(_mappingService.GetKeyDisplayName((int)key));
|
||||
}
|
||||
|
||||
if (actionKey.HasValue)
|
||||
{
|
||||
keyList.Add(_mappingService.GetKeyDisplayName((int)actionKey.Value));
|
||||
}
|
||||
|
||||
if (actionKeyChord.HasValue && _activeTarget.AllowChords)
|
||||
{
|
||||
keyList.Add(_mappingService.GetKeyDisplayName((int)actionKeyChord.Value));
|
||||
}
|
||||
|
||||
return keyList;
|
||||
}
|
||||
|
||||
private void RemoveExistingModifierVariant(VirtualKey key)
|
||||
{
|
||||
KeyType keyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)key);
|
||||
|
||||
// No need to remove if the key is an action key
|
||||
if (keyType == KeyType.Action)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var existingKey in _currentlyPressedKeys.ToList())
|
||||
{
|
||||
if (existingKey != key)
|
||||
{
|
||||
KeyType existingKeyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)existingKey);
|
||||
|
||||
// Remove the existing key if it is a modifier key and has the same type as the new key
|
||||
if (existingKeyType == keyType)
|
||||
{
|
||||
_currentlyPressedKeys.Remove(existingKey);
|
||||
_keyPressOrder.Remove(existingKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CleanupHook();
|
||||
_mappingService?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IKeyboardHookTarget
|
||||
{
|
||||
bool AllowChords { get; }
|
||||
|
||||
void OnKeyDown(VirtualKey key, List<string> formattedKeys);
|
||||
|
||||
void OnKeyUp(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
}
|
||||
|
||||
void ClearKeys();
|
||||
|
||||
void OnInputLimitReached();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class ProgramShortcut : IToggleableShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public string AppToRun { get; set; } = string.Empty;
|
||||
|
||||
public string Args { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
|
||||
public string StartInDirectory { get; set; } = string.Empty;
|
||||
|
||||
public string Elevation { get; set; } = string.Empty;
|
||||
|
||||
public string IfRunningAction { get; set; } = string.Empty;
|
||||
|
||||
public string Visibility { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public partial class Remapping : INotifyPropertyChanged, IToggleableShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public List<string> RemappedKeys { get; set; } = new List<string>();
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
|
||||
private bool IsEnabledValue { get; set; } = true;
|
||||
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => IsEnabledValue;
|
||||
set
|
||||
{
|
||||
if (IsEnabledValue != value)
|
||||
{
|
||||
IsEnabledValue = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using KeyboardManagerEditorUI.Settings;
|
||||
using ManagedCommon;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public static class RemappingHelper
|
||||
{
|
||||
public static bool SaveMapping(KeyboardMappingService mappingService, List<string> originalKeys, List<string> remappedKeys, bool isAppSpecific, string appName, bool saveToSettings = true)
|
||||
{
|
||||
if (mappingService == null)
|
||||
{
|
||||
Logger.LogError("Mapping service is null, cannot save mapping");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (originalKeys == null || originalKeys.Count == 0 || remappedKeys == null || remappedKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (originalKeys.Count == 1)
|
||||
{
|
||||
int originalKey = mappingService.GetKeyCodeFromName(originalKeys[0]);
|
||||
|
||||
if (originalKey != 0)
|
||||
{
|
||||
string targetKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapShortcut,
|
||||
OriginalKeys = originalKey.ToString(CultureInfo.InvariantCulture),
|
||||
TargetKeys = targetKeysString,
|
||||
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||
};
|
||||
if (remappedKeys.Count == 1)
|
||||
{
|
||||
int targetKey = mappingService.GetKeyCodeFromName(remappedKeys[0]);
|
||||
if (targetKey != 0)
|
||||
{
|
||||
mappingService.AddSingleKeyMapping(originalKey, targetKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingService.AddSingleKeyMapping(originalKey, targetKeysString);
|
||||
}
|
||||
|
||||
if (saveToSettings)
|
||||
{
|
||||
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string originalKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
string targetKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapShortcut,
|
||||
OriginalKeys = originalKeysString,
|
||||
TargetKeys = targetKeysString,
|
||||
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||
};
|
||||
|
||||
if (isAppSpecific && !string.IsNullOrEmpty(appName))
|
||||
{
|
||||
mappingService.AddShortcutMapping(originalKeysString, targetKeysString, appName);
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingService.AddShortcutMapping(originalKeysString, targetKeysString);
|
||||
}
|
||||
|
||||
if (saveToSettings)
|
||||
{
|
||||
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||
}
|
||||
}
|
||||
|
||||
return mappingService.SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error saving mapping: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DeleteRemapping(KeyboardMappingService mappingService, Remapping remapping, bool deleteFromSettings = true)
|
||||
{
|
||||
if (mappingService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (remapping.Shortcut.Count == 1)
|
||||
{
|
||||
// Single key mapping
|
||||
int originalKey = mappingService.GetKeyCodeFromName(remapping.Shortcut[0]);
|
||||
if (originalKey != 0)
|
||||
{
|
||||
if (mappingService.DeleteSingleKeyMapping(originalKey))
|
||||
{
|
||||
if (deleteFromSettings)
|
||||
{
|
||||
SettingsManager.RemoveShortcutKeyMappingFromSettings(remapping.Id);
|
||||
}
|
||||
|
||||
return mappingService.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (remapping.Shortcut.Count > 1)
|
||||
{
|
||||
// Shortcut mapping
|
||||
string originalKeysString = string.Join(";", remapping.Shortcut.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
bool deleteResult;
|
||||
if (!remapping.IsAllApps && !string.IsNullOrEmpty(remapping.AppName))
|
||||
{
|
||||
// App-specific shortcut key mapping
|
||||
deleteResult = mappingService.DeleteShortcutMapping(originalKeysString, remapping.AppName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Global shortcut key mapping
|
||||
deleteResult = mappingService.DeleteShortcutMapping(originalKeysString);
|
||||
}
|
||||
|
||||
if (deleteResult && deleteFromSettings)
|
||||
{
|
||||
SettingsManager.RemoveShortcutKeyMappingFromSettings(remapping.Id);
|
||||
}
|
||||
|
||||
return deleteResult ? mappingService.SaveSettings() : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error deleting remapping: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsModifierKey(VirtualKey key)
|
||||
{
|
||||
return key == VirtualKey.Control
|
||||
|| key == VirtualKey.LeftControl
|
||||
|| key == VirtualKey.RightControl
|
||||
|| key == VirtualKey.Menu
|
||||
|| key == VirtualKey.LeftMenu
|
||||
|| key == VirtualKey.RightMenu
|
||||
|| key == VirtualKey.Shift
|
||||
|| key == VirtualKey.LeftShift
|
||||
|| key == VirtualKey.RightShift
|
||||
|| key == VirtualKey.LeftWindows
|
||||
|| key == VirtualKey.RightWindows;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class TextMapping : IToggleableShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public string Id { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class URLShortcut : IToggleableShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public string URL { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public enum ValidationErrorType
|
||||
{
|
||||
NoError,
|
||||
EmptyOriginalKeys,
|
||||
EmptyRemappedKeys,
|
||||
ModifierOnly,
|
||||
EmptyAppName,
|
||||
IllegalShortcut,
|
||||
DuplicateMapping,
|
||||
SelfMapping,
|
||||
EmptyTargetText,
|
||||
EmptyUrl,
|
||||
EmptyProgramPath,
|
||||
OneKeyMapping,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using KeyboardManagerEditorUI.Settings;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public static class ValidationHelper
|
||||
{
|
||||
public static readonly Dictionary<ValidationErrorType, (string Title, string Message)> ValidationMessages = new()
|
||||
{
|
||||
{ ValidationErrorType.EmptyOriginalKeys, ("Missing Original Keys", "Please enter at least one original key to create a remapping.") },
|
||||
{ ValidationErrorType.EmptyRemappedKeys, ("Missing Target Keys", "Please enter at least one target key to create a remapping.") },
|
||||
{ ValidationErrorType.ModifierOnly, ("Invalid Shortcut", "Shortcuts must contain at least one action key in addition to modifier keys (Ctrl, Alt, Shift, Win).") },
|
||||
{ ValidationErrorType.EmptyAppName, ("Missing Application Name", "You've selected app-specific remapping but haven't specified an application name. Please enter the application name.") },
|
||||
{ ValidationErrorType.IllegalShortcut, ("Reserved System Shortcut", "Win+L and Ctrl+Alt+Delete are reserved system shortcuts and cannot be remapped.") },
|
||||
{ ValidationErrorType.DuplicateMapping, ("Duplicate Remapping", "This key or shortcut is already remapped.") },
|
||||
{ ValidationErrorType.SelfMapping, ("Invalid Remapping", "A key or shortcut cannot be remapped to itself. Please choose a different target.") },
|
||||
{ ValidationErrorType.EmptyTargetText, ("Missing Target Text", "Please enter the text to be inserted when the shortcut is pressed.") },
|
||||
{ ValidationErrorType.EmptyUrl, ("Missing URL", "Please enter the URL to open when the shortcut is pressed.") },
|
||||
{ ValidationErrorType.EmptyProgramPath, ("Missing Program Path", "Please enter the program path to launch when the shortcut is pressed.") },
|
||||
{ ValidationErrorType.OneKeyMapping, ("Invalid Remapping", "A single key cannot be remapped to a Program or URL shortcut. Please choose a combination of keys.") },
|
||||
};
|
||||
|
||||
public static ValidationErrorType ValidateKeyMapping(
|
||||
List<string> originalKeys,
|
||||
List<string> remappedKeys,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
if (originalKeys == null || originalKeys.Count == 0)
|
||||
{
|
||||
return ValidationErrorType.EmptyOriginalKeys;
|
||||
}
|
||||
|
||||
if (remappedKeys == null || remappedKeys.Count == 0)
|
||||
{
|
||||
return ValidationErrorType.EmptyRemappedKeys;
|
||||
}
|
||||
|
||||
if ((originalKeys.Count > 1 && ContainsOnlyModifierKeys(originalKeys)) ||
|
||||
(remappedKeys.Count > 1 && ContainsOnlyModifierKeys(remappedKeys)))
|
||||
{
|
||||
return ValidationErrorType.ModifierOnly;
|
||||
}
|
||||
|
||||
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
|
||||
{
|
||||
return ValidationErrorType.EmptyAppName;
|
||||
}
|
||||
|
||||
if (originalKeys.Count > 1 && IsIllegalShortcut(originalKeys, mappingService))
|
||||
{
|
||||
return ValidationErrorType.IllegalShortcut;
|
||||
}
|
||||
|
||||
if (IsDuplicateMapping(originalKeys, isEditMode, mappingService, appName))
|
||||
{
|
||||
return ValidationErrorType.DuplicateMapping;
|
||||
}
|
||||
|
||||
if (IsSelfMapping(originalKeys, remappedKeys, mappingService))
|
||||
{
|
||||
return ValidationErrorType.SelfMapping;
|
||||
}
|
||||
|
||||
return ValidationErrorType.NoError;
|
||||
}
|
||||
|
||||
public static ValidationErrorType ValidateTextMapping(
|
||||
List<string> keys,
|
||||
string textContent,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false)
|
||||
{
|
||||
if (keys == null || keys.Count == 0)
|
||||
{
|
||||
return ValidationErrorType.EmptyOriginalKeys;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(textContent))
|
||||
{
|
||||
return ValidationErrorType.EmptyTargetText;
|
||||
}
|
||||
|
||||
if (keys.Count > 1 && ContainsOnlyModifierKeys(keys))
|
||||
{
|
||||
return ValidationErrorType.ModifierOnly;
|
||||
}
|
||||
|
||||
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
|
||||
{
|
||||
return ValidationErrorType.EmptyAppName;
|
||||
}
|
||||
|
||||
if (keys.Count > 1 && IsIllegalShortcut(keys, mappingService))
|
||||
{
|
||||
return ValidationErrorType.IllegalShortcut;
|
||||
}
|
||||
|
||||
if (IsDuplicateMapping(keys, isEditMode, mappingService, appName))
|
||||
{
|
||||
return ValidationErrorType.DuplicateMapping;
|
||||
}
|
||||
|
||||
return ValidationErrorType.NoError;
|
||||
}
|
||||
|
||||
public static ValidationErrorType ValidateUrlMapping(
|
||||
List<string> originalKeys,
|
||||
string url,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
return ValidationErrorType.EmptyUrl;
|
||||
}
|
||||
|
||||
return ValidateProgramOrUrlMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||
}
|
||||
|
||||
public static ValidationErrorType ValidateAppMapping(
|
||||
List<string> originalKeys,
|
||||
string programPath,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(programPath))
|
||||
{
|
||||
return ValidationErrorType.EmptyProgramPath;
|
||||
}
|
||||
|
||||
return ValidateProgramOrUrlMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||
}
|
||||
|
||||
public static bool IsDuplicateMapping(List<string> keys, bool isEditMode, KeyboardMappingService mappingService, string appName)
|
||||
{
|
||||
int upperLimit = isEditMode ? 1 : 0;
|
||||
string shortcutKeysString = BuildKeyCodeString(keys, mappingService);
|
||||
return SettingsManager.EditorSettings.ShortcutSettingsDictionary.Values
|
||||
.Count(settings => KeyboardManagerInterop.AreShortcutsEqual(settings.Shortcut.OriginalKeys, shortcutKeysString) &&
|
||||
(string.IsNullOrEmpty(settings.Shortcut.TargetApp) || string.IsNullOrEmpty(appName) || settings.Shortcut.TargetApp == appName)) > upperLimit;
|
||||
}
|
||||
|
||||
public static bool IsSelfMapping(List<string> originalKeys, List<string> remappedKeys, KeyboardMappingService mappingService)
|
||||
{
|
||||
if (mappingService == null || originalKeys == null || remappedKeys == null ||
|
||||
originalKeys.Count == 0 || remappedKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string originalKeysString = BuildKeyCodeString(originalKeys, mappingService);
|
||||
string remappedKeysString = BuildKeyCodeString(remappedKeys, mappingService);
|
||||
|
||||
return KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, remappedKeysString);
|
||||
}
|
||||
|
||||
public static bool ContainsOnlyModifierKeys(List<string> keys)
|
||||
{
|
||||
if (keys == null || keys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return keys.All(key =>
|
||||
{
|
||||
int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(key);
|
||||
var keyType = (KeyType)KeyboardManagerInterop.GetKeyType(keyCode);
|
||||
return keyType != KeyType.Action;
|
||||
});
|
||||
}
|
||||
|
||||
public static bool IsKeyOrphaned(int originalKey, KeyboardMappingService mappingService)
|
||||
{
|
||||
// Check single key mappings
|
||||
foreach (var mapping in mappingService.GetSingleKeyMappings())
|
||||
{
|
||||
if (!mapping.IsShortcut && int.TryParse(mapping.TargetKey, out int targetKey) && targetKey == originalKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check shortcut mappings
|
||||
foreach (var mapping in mappingService.GetShortcutMappings())
|
||||
{
|
||||
string[] targetKeys = mapping.TargetKeys.Split(';');
|
||||
if (targetKeys.Length == 1 && int.TryParse(targetKeys[0], out int shortcutTargetKey) && shortcutTargetKey == originalKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ValidationErrorType ValidateProgramOrUrlMapping(
|
||||
List<string> originalKeys,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
if (originalKeys.Count < 2)
|
||||
{
|
||||
return ValidationErrorType.OneKeyMapping;
|
||||
}
|
||||
|
||||
ValidationErrorType error = ValidateKeyMapping(originalKeys, originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||
|
||||
return error == ValidationErrorType.SelfMapping ? ValidationErrorType.NoError : error;
|
||||
}
|
||||
|
||||
private static bool IsIllegalShortcut(List<string> keys, KeyboardMappingService mappingService)
|
||||
{
|
||||
string shortcutKeysString = BuildKeyCodeString(keys, mappingService);
|
||||
Logger.LogInfo($"Checking if shortcut is illegal: {shortcutKeysString}");
|
||||
return KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString);
|
||||
}
|
||||
|
||||
private static string BuildKeyCodeString(List<string> keys, KeyboardMappingService mappingService)
|
||||
{
|
||||
return string.Join(";", keys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyMapping
|
||||
{
|
||||
public int OriginalKey { get; set; }
|
||||
|
||||
public string TargetKey { get; set; } = string.Empty;
|
||||
|
||||
public bool IsShortcut { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyToTextMapping
|
||||
{
|
||||
public int OriginalKey { get; set; }
|
||||
|
||||
public string TargetText { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public enum KeyType
|
||||
{
|
||||
Win = 0,
|
||||
Ctrl = 1,
|
||||
Alt = 2,
|
||||
Shift = 3,
|
||||
Action = 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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 System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public static class KeyboardManagerInterop
|
||||
{
|
||||
private const string DllName = "Powertoys.KeyboardManagerEditorLibraryWrapper.dll";
|
||||
private const CallingConvention Convention = CallingConvention.Cdecl;
|
||||
|
||||
// Configuration Management
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern IntPtr CreateMappingConfiguration();
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern void DestroyMappingConfiguration(IntPtr config);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool LoadMappingSettings(IntPtr config);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SaveMappingSettings(IntPtr config);
|
||||
|
||||
// Get Mapping Functions
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern int GetSingleKeyRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetSingleKeyRemap(IntPtr config, int index, ref SingleKeyMapping mapping);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern int GetSingleKeyToTextRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetSingleKeyToTextRemap(IntPtr config, int index, ref KeyboardTextMapping mapping);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern int GetShortcutRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetShortcutRemap(IntPtr config, int index, ref ShortcutMapping mapping);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern int GetShortcutRemapCountByType(IntPtr config, int operationType);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetShortcutRemapByType(IntPtr config, int operationType, int index, ref ShortcutMapping mapping);
|
||||
|
||||
// Add Mapping Functions
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyRemap(IntPtr config, int originalKey, int targetKey);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyToTextRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetText);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyToShortcutRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetKeys);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddShortcutRemap(
|
||||
IntPtr config,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string originalKeys,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetKeys,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetApp,
|
||||
int operationType = 0,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string appPathOrUri = "",
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? args = null,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? startDirectory = null,
|
||||
int elevation = 0,
|
||||
int ifRunningAction = 0,
|
||||
int visibility = 0);
|
||||
|
||||
// Delete Mapping Functions
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteSingleKeyRemap(IntPtr mappingConfiguration, int originalKey);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteSingleKeyToTextRemap(IntPtr config, int originalKey);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteShortcutRemap(IntPtr mappingConfiguration, [MarshalAs(UnmanagedType.LPWStr)] string originalKeys, [MarshalAs(UnmanagedType.LPWStr)] string targetApp);
|
||||
|
||||
// Key Utility Functions
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
internal static extern int GetKeyCodeFromName([MarshalAs(UnmanagedType.LPWStr)] string keyName);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
internal static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern int GetKeyType(int keyCode);
|
||||
|
||||
// Validation Functions
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsShortcutIllegal([MarshalAs(UnmanagedType.LPWStr)] string shortcutKeys);
|
||||
|
||||
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AreShortcutsEqual([MarshalAs(UnmanagedType.LPWStr)] string lShort, [MarshalAs(UnmanagedType.LPWStr)] string rShortcut);
|
||||
|
||||
// String Management Functions
|
||||
[DllImport(DllName, CallingConvention = Convention)]
|
||||
internal static extern void FreeString(IntPtr str);
|
||||
|
||||
public static string GetStringAndFree(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string? result = Marshal.PtrToStringUni(handle);
|
||||
FreeString(handle);
|
||||
return result ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SingleKeyMapping
|
||||
{
|
||||
public int OriginalKey;
|
||||
public IntPtr TargetKey;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool IsShortcut;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct KeyboardTextMapping
|
||||
{
|
||||
public int OriginalKey;
|
||||
public IntPtr TargetText;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct ShortcutMapping
|
||||
{
|
||||
public IntPtr OriginalKeys;
|
||||
public IntPtr TargetKeys;
|
||||
public IntPtr TargetApp;
|
||||
public int OperationType;
|
||||
public IntPtr TargetText;
|
||||
public IntPtr ProgramPath;
|
||||
public IntPtr ProgramArgs;
|
||||
public IntPtr UriToOpen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyboardMappingService : IDisposable
|
||||
{
|
||||
private IntPtr _configHandle;
|
||||
private bool _disposed;
|
||||
|
||||
public KeyboardMappingService()
|
||||
{
|
||||
_configHandle = KeyboardManagerInterop.CreateMappingConfiguration();
|
||||
if (_configHandle == IntPtr.Zero)
|
||||
{
|
||||
Logger.LogError("Failed to create mapping configuration");
|
||||
throw new InvalidOperationException("Failed to create mapping configuration");
|
||||
}
|
||||
|
||||
KeyboardManagerInterop.LoadMappingSettings(_configHandle);
|
||||
}
|
||||
|
||||
public List<KeyMapping> GetSingleKeyMappings()
|
||||
{
|
||||
var result = new List<KeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetSingleKeyRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(SingleKeyMapping);
|
||||
if (KeyboardManagerInterop.GetSingleKeyRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new KeyMapping
|
||||
{
|
||||
OriginalKey = mapping.OriginalKey,
|
||||
TargetKey = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKey),
|
||||
IsShortcut = mapping.IsShortcut,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ShortcutKeyMapping> GetShortcutMappings()
|
||||
{
|
||||
var result = new List<ShortcutKeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetShortcutRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(ShortcutMapping);
|
||||
if (KeyboardManagerInterop.GetShortcutRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new ShortcutKeyMapping
|
||||
{
|
||||
OriginalKeys = KeyboardManagerInterop.GetStringAndFree(mapping.OriginalKeys),
|
||||
TargetKeys = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKeys),
|
||||
TargetApp = KeyboardManagerInterop.GetStringAndFree(mapping.TargetApp),
|
||||
OperationType = (ShortcutOperationType)mapping.OperationType,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
ProgramPath = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramPath),
|
||||
ProgramArgs = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramArgs),
|
||||
UriToOpen = KeyboardManagerInterop.GetStringAndFree(mapping.UriToOpen),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ShortcutKeyMapping> GetShortcutMappingsByType(ShortcutOperationType operationType)
|
||||
{
|
||||
var result = new List<ShortcutKeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetShortcutRemapCountByType(_configHandle, (int)operationType);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(ShortcutMapping);
|
||||
if (KeyboardManagerInterop.GetShortcutRemapByType(_configHandle, (int)operationType, i, ref mapping))
|
||||
{
|
||||
result.Add(new ShortcutKeyMapping
|
||||
{
|
||||
OriginalKeys = KeyboardManagerInterop.GetStringAndFree(mapping.OriginalKeys),
|
||||
TargetKeys = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKeys),
|
||||
TargetApp = KeyboardManagerInterop.GetStringAndFree(mapping.TargetApp),
|
||||
OperationType = (ShortcutOperationType)mapping.OperationType,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
ProgramPath = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramPath),
|
||||
ProgramArgs = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramArgs),
|
||||
UriToOpen = KeyboardManagerInterop.GetStringAndFree(mapping.UriToOpen),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<KeyToTextMapping> GetKeyToTextMappings()
|
||||
{
|
||||
var result = new List<KeyToTextMapping>();
|
||||
int count = KeyboardManagerInterop.GetSingleKeyToTextRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(KeyboardTextMapping);
|
||||
if (KeyboardManagerInterop.GetSingleKeyToTextRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new KeyToTextMapping
|
||||
{
|
||||
OriginalKey = mapping.OriginalKey,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetKeyDisplayName(int keyCode)
|
||||
{
|
||||
var keyName = new StringBuilder(64);
|
||||
KeyboardManagerInterop.GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
|
||||
return keyName.ToString();
|
||||
}
|
||||
|
||||
public int GetKeyCodeFromName(string keyName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyName))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(keyName);
|
||||
Logger.LogInfo($"Key code for key name {keyName}: {keyCode}");
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
public bool AddSingleKeyMapping(int originalKey, int targetKey)
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
|
||||
}
|
||||
|
||||
public bool AddSingleKeyMapping(int originalKey, string targetKeys)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!targetKeys.Contains(';') && int.TryParse(targetKeys, out int targetKey))
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyToShortcutRemap(_configHandle, originalKey, targetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddSingleKeyToTextMapping(int originalKey, string targetText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddSingleKeyToTextRemap(_configHandle, originalKey, targetText);
|
||||
}
|
||||
|
||||
public bool AddShortcutMapping(string originalKeys, string targetKeys, string targetApp = "", ShortcutOperationType operationType = ShortcutOperationType.RemapShortcut)
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalKeys) || string.IsNullOrEmpty(targetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddShortcutRemap(_configHandle, originalKeys, targetKeys, targetApp, (int)operationType);
|
||||
}
|
||||
|
||||
public bool AddShortcutMapping(ShortcutKeyMapping shortcutKeyMapping)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutKeyMapping.OriginalKeys) || string.IsNullOrEmpty(shortcutKeyMapping.TargetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shortcutKeyMapping.OperationType == ShortcutOperationType.RunProgram && string.IsNullOrEmpty(shortcutKeyMapping.ProgramPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shortcutKeyMapping.OperationType == ShortcutOperationType.OpenUri && string.IsNullOrEmpty(shortcutKeyMapping.UriToOpen))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shortcutKeyMapping.OperationType == ShortcutOperationType.RunProgram)
|
||||
{
|
||||
return KeyboardManagerInterop.AddShortcutRemap(
|
||||
_configHandle,
|
||||
shortcutKeyMapping.OriginalKeys,
|
||||
shortcutKeyMapping.TargetKeys,
|
||||
shortcutKeyMapping.TargetApp,
|
||||
(int)shortcutKeyMapping.OperationType,
|
||||
shortcutKeyMapping.ProgramPath,
|
||||
string.IsNullOrEmpty(shortcutKeyMapping.ProgramArgs) ? null : shortcutKeyMapping.ProgramArgs,
|
||||
string.IsNullOrEmpty(shortcutKeyMapping.StartInDirectory) ? null : shortcutKeyMapping.StartInDirectory,
|
||||
(int)shortcutKeyMapping.Elevation,
|
||||
(int)shortcutKeyMapping.IfRunningAction,
|
||||
(int)shortcutKeyMapping.Visibility);
|
||||
}
|
||||
else if (shortcutKeyMapping.OperationType == ShortcutOperationType.OpenUri)
|
||||
{
|
||||
return KeyboardManagerInterop.AddShortcutRemap(
|
||||
_configHandle,
|
||||
shortcutKeyMapping.OriginalKeys,
|
||||
shortcutKeyMapping.TargetKeys,
|
||||
shortcutKeyMapping.TargetApp,
|
||||
(int)shortcutKeyMapping.OperationType,
|
||||
shortcutKeyMapping.UriToOpen);
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddShortcutRemap(
|
||||
_configHandle,
|
||||
shortcutKeyMapping.OriginalKeys,
|
||||
shortcutKeyMapping.TargetKeys,
|
||||
shortcutKeyMapping.TargetApp,
|
||||
(int)shortcutKeyMapping.OperationType);
|
||||
}
|
||||
|
||||
public bool SaveSettings()
|
||||
{
|
||||
return KeyboardManagerInterop.SaveMappingSettings(_configHandle);
|
||||
}
|
||||
|
||||
public bool DeleteSingleKeyMapping(int originalKey)
|
||||
{
|
||||
return KeyboardManagerInterop.DeleteSingleKeyRemap(_configHandle, originalKey);
|
||||
}
|
||||
|
||||
public bool DeleteSingleKeyToTextMapping(int originalKey)
|
||||
{
|
||||
if (originalKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.DeleteSingleKeyToTextRemap(_configHandle, originalKey);
|
||||
}
|
||||
|
||||
public bool DeleteShortcutMapping(string originalKeys, string targetApp = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.DeleteShortcutRemap(_configHandle, originalKeys, targetApp ?? string.Empty);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_configHandle != IntPtr.Zero)
|
||||
{
|
||||
KeyboardManagerInterop.DestroyMappingConfiguration(_configHandle);
|
||||
_configHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~KeyboardMappingService()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class ShortcutKeyMapping
|
||||
{
|
||||
public string OriginalKeys { get; set; } = string.Empty;
|
||||
|
||||
public string TargetKeys { get; set; } = string.Empty;
|
||||
|
||||
public string TargetApp { get; set; } = string.Empty;
|
||||
|
||||
public ShortcutOperationType OperationType { get; set; }
|
||||
|
||||
public string TargetText { get; set; } = string.Empty;
|
||||
|
||||
public string ProgramPath { get; set; } = string.Empty;
|
||||
|
||||
public string ProgramArgs { get; set; } = string.Empty;
|
||||
|
||||
public string StartInDirectory { get; set; } = string.Empty;
|
||||
|
||||
public ElevationLevel Elevation { get; set; } = ElevationLevel.NonElevated;
|
||||
|
||||
public ProgramAlreadyRunningAction IfRunningAction { get; set; } = ProgramAlreadyRunningAction.ShowWindow;
|
||||
|
||||
public StartWindowType Visibility { get; set; } = StartWindowType.Normal;
|
||||
|
||||
public string UriToOpen { get; set; } = string.Empty;
|
||||
|
||||
public enum ElevationLevel
|
||||
{
|
||||
NonElevated = 0,
|
||||
Elevated = 1,
|
||||
DifferentUser = 2,
|
||||
}
|
||||
|
||||
public enum StartWindowType
|
||||
{
|
||||
Normal = 0,
|
||||
Hidden = 1,
|
||||
Minimized = 2,
|
||||
Maximized = 3,
|
||||
}
|
||||
|
||||
public enum ProgramAlreadyRunningAction
|
||||
{
|
||||
ShowWindow = 0,
|
||||
StartAnother = 1,
|
||||
DoNothing = 2,
|
||||
Close = 3,
|
||||
EndTask = 4,
|
||||
CloseAndEndTask = 5,
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not ShortcutKeyMapping other)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return OriginalKeys == other.OriginalKeys &&
|
||||
TargetKeys == other.TargetKeys &&
|
||||
TargetApp == other.TargetApp &&
|
||||
OperationType == other.OperationType &&
|
||||
TargetText == other.TargetText &&
|
||||
ProgramPath == other.ProgramPath &&
|
||||
ProgramArgs == other.ProgramArgs &&
|
||||
StartInDirectory == other.StartInDirectory &&
|
||||
Elevation == other.Elevation &&
|
||||
IfRunningAction == other.IfRunningAction &&
|
||||
Visibility == other.Visibility &&
|
||||
UriToOpen == other.UriToOpen;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hash = default(HashCode);
|
||||
hash.Add(OriginalKeys);
|
||||
hash.Add(TargetKeys);
|
||||
hash.Add(TargetApp);
|
||||
hash.Add(OperationType);
|
||||
hash.Add(TargetText);
|
||||
hash.Add(ProgramPath);
|
||||
hash.Add(ProgramArgs);
|
||||
hash.Add(StartInDirectory);
|
||||
hash.Add(Elevation);
|
||||
hash.Add(IfRunningAction);
|
||||
hash.Add(Visibility);
|
||||
hash.Add(UriToOpen);
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public enum ShortcutOperationType
|
||||
{
|
||||
RemapShortcut = 0,
|
||||
RunProgram = 1,
|
||||
OpenUri = 2,
|
||||
RemapText = 3,
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,36 @@
|
||||
<RootNamespace>KeyboardManagerEditorUI</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<Nullable>enable</Nullable>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<AssemblyName>PowerToys.KeyboardManagerEditorUI</AssemblyName>
|
||||
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\$(MSBuildProjectName)</OutputPath>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>PowerToys.KeyboardManagerEditorUI.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
|
||||
<EnableXamlJitOptimization>true</EnableXamlJitOptimization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\KeyboardManagerEditor\Keyboard.ico" />
|
||||
<None Remove="Styles\Colors.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Remove="KeyboardManagerEditorXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="KeyboardManagerEditorXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
@@ -27,12 +47,16 @@
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="CommunityToolkit.Common" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
|
||||
<PackageReference Include="Microsoft.Web.WebView2" />
|
||||
</ItemGroup>
|
||||
@@ -40,9 +64,26 @@
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\Common.UI.Controls\Common.UI.Controls.csproj" />
|
||||
<ProjectReference Include="..\KeyboardManagerEditorLibraryWrapper\KeyboardManagerEditorLibraryWrapper.vcxproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\" />
|
||||
<Content Update="Assets\KeyboardManagerEditor\Keyboard.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\KeyboardManagerEditor\Square150x150Logo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\Colors.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\Button.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
@@ -50,7 +91,7 @@
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application
|
||||
x:Class="KeyboardManagerEditorUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="/Controls/IconLabelControl.xaml" />
|
||||
<ResourceDictionary Source="/Styles/Button.xaml" />
|
||||
<ResourceDictionary Source="/Styles/Colors.xaml" />
|
||||
<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.MergedDictionaries>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">960</x:Double>
|
||||
<!-- Icons -->
|
||||
<x:String x:Key="ArrowIconData">M12.001 2C17.5238 2 22.001 6.47715 22.001 12C22.001 17.5228 17.5238 22 12.001 22C6.47813 22 2.00098 17.5228 2.00098 12C2.00098 6.47715 6.47813 2 12.001 2ZM12.7813 7.46897L12.6972 7.39635C12.4362 7.2027 12.078 7.20031 11.8146 7.38918L11.7206 7.46897L11.648 7.55308C11.4544 7.81407 11.452 8.17229 11.6409 8.43568L11.7206 8.52963L14.4403 11.2493H7.75027L7.6485 11.2561C7.31571 11.3013 7.05227 11.5647 7.00712 11.8975L7.00027 11.9993L7.00712 12.1011C7.05227 12.4339 7.31571 12.6973 7.6485 12.7424L7.75027 12.7493H14.4403L11.72 15.4697L11.6474 15.5538C11.4295 15.8474 11.4536 16.264 11.7198 16.5303C11.9861 16.7967 12.4027 16.8209 12.6964 16.6032L12.7805 16.5306L16.782 12.5306L16.8547 12.4464C17.0484 12.1854 17.0508 11.8272 16.8619 11.5638L16.7821 11.4698L12.7813 7.46897L12.6972 7.39635L12.7813 7.46897Z</x:String>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -7,7 +7,12 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using KeyboardManagerEditorUI.Settings;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -29,14 +34,22 @@ namespace KeyboardManagerEditorUI
|
||||
public partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor logger is initialized");
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||
});
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
|
||||
SettingsManager.CorrelateServiceAndEditorMappings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,11 +58,28 @@ namespace KeyboardManagerEditorUI
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
window = new MainWindow();
|
||||
window.Activate();
|
||||
MainWindow = new MainWindow();
|
||||
|
||||
MainWindow.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
MainWindow.Activate();
|
||||
MainWindow.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
(MainWindow.Content as FrameworkElement)?.UpdateLayout();
|
||||
});
|
||||
});
|
||||
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor window is launched");
|
||||
}
|
||||
|
||||
private Window? window;
|
||||
/// <summary>
|
||||
/// Log the unhandled exception for the editor.
|
||||
/// </summary>
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
internal static MainWindow MainWindow { get; private set; } = null!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<winuiex:WindowEx
|
||||
x:Class="KeyboardManagerEditorUI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:KeyboardManagerEditorUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="KeyboardManagerEditorUI"
|
||||
Width="1440"
|
||||
Height="900"
|
||||
MinWidth="860"
|
||||
MinHeight="320"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
<Grid
|
||||
x:Name="LayoutRoot"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="titleBar" Title="Keyboard Manager">
|
||||
<TitleBar.IconSource>
|
||||
<ImageIconSource ImageSource="/Assets/KeyboardManagerEditor/FluentIconsKeyboardManager.png" />
|
||||
</TitleBar.IconSource>
|
||||
</TitleBar>
|
||||
|
||||
<pages:MainPage
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch" />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using WinUIEx;
|
||||
|
||||
namespace KeyboardManagerEditorUI
|
||||
{
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
SetTitleBar();
|
||||
this.Activated += MainWindow_Activated;
|
||||
this.Closed += MainWindow_Closed;
|
||||
}
|
||||
|
||||
private void SetTitleBar()
|
||||
{
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
this.SetIcon(@"Assets\KeyboardManagerEditor\Keyboard.ico");
|
||||
this.SetTitleBar(titleBar);
|
||||
Title = "Keyboard Manager";
|
||||
}
|
||||
|
||||
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
if (args.WindowActivationState == WindowActivationState.Deactivated)
|
||||
{
|
||||
// Release the keyboard hook when the window is deactivated
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
KeyboardHookHelper.Instance.Dispose();
|
||||
this.Activated -= MainWindow_Activated;
|
||||
this.Closed -= MainWindow_Closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="KeyboardManagerEditorUI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:KeyboardManagerEditorUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="KeyboardManagerEditorUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="myButton" Click="MyButton_Click">Click Me</Button>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -1,42 +0,0 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace KeyboardManagerEditorUI
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
[DllImport("KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool CheckIfRemappingsAreValid();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void MyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Call the C++ function to check if the current remappings are valid
|
||||
myButton.Content = CheckIfRemappingsAreValid() ? "Valid" : "Invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,9 @@
|
||||
<mp:PhoneIdentity PhoneProductId="edb1d2cd-ef93-4f89-9db6-4edf04ff20a5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>KeyboardManagerEditorUI</DisplayName>
|
||||
<DisplayName>Keyboard Manager</DisplayName>
|
||||
<PublisherDisplayName>haoliuu</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
<Logo>Assets\KeyboardManagerEditor\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
@@ -34,13 +34,13 @@
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="KeyboardManagerEditorUI"
|
||||
Description="KeyboardManagerEditorUI"
|
||||
DisplayName="Keyboard Manager"
|
||||
Description="Keyboard Manager"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
Square150x150Logo="Assets\KeyboardManagerEditor\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\KeyboardManagerEditor\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\KeyboardManagerEditor\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\KeyboardManagerEditor\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="KeyboardManagerEditorUI.Pages.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:commoncontrols="using:Microsoft.PowerToys.Common.UI.Controls"
|
||||
xmlns:controls="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">800</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">800</x:Double>
|
||||
<Style
|
||||
x:Key="OriginalKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
||||
TargetType="commoncontrols:KeyVisual">
|
||||
<Setter Property="Padding" Value="6" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
|
||||
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="RenderKeyAsGlyph" Value="True" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="RemappedKeyVisualStyle"
|
||||
BasedOn="{StaticResource OriginalKeyVisualStyle}"
|
||||
TargetType="commoncontrols:KeyVisual">
|
||||
<Setter Property="Background" Value="{ThemeResource CustomAccentBackgroundBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="RemappedIconLabelControlStyle"
|
||||
BasedOn="{StaticResource DefaultIconLabelControlStyle}"
|
||||
TargetType="controls:IconLabelControl">
|
||||
<Setter Property="Background" Value="{ThemeResource CustomAccentBackgroundBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="6" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||
</Style>
|
||||
<Style x:Key="ItemDividerStyle" TargetType="Rectangle">
|
||||
<Style.Setters>
|
||||
<Setter Property="Grid.ColumnSpan" Value="2" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="-16,0,-16,-8" />
|
||||
<Setter Property="Fill" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<tkconverters:DoubleToVisibilityConverter
|
||||
x:Key="CountToVisibilityConverter"
|
||||
FalseValue="Collapsed"
|
||||
GreaterThan="0"
|
||||
TrueValue="Visible" />
|
||||
<tkconverters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
</Page.Resources>
|
||||
|
||||
<Grid Padding="16" RowSpacing="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Button
|
||||
x:Name="NewRemappingBtn"
|
||||
VerticalAlignment="Top"
|
||||
Click="NewRemappingBtn_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="NewRemappingBtn_Text" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource LayerFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<tkcontrols:SwitchPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
TargetType="x:String"
|
||||
Value="{x:Bind MappingState, Mode=OneWay}">
|
||||
<tkcontrols:Case Value="Empty">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="8">
|
||||
<FontIcon
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="24"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
x:Uid="EmptyStateTitle"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
x:Uid="EmptyStateDescription"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</tkcontrols:Case>
|
||||
<tkcontrols:Case Value="HasMappings">
|
||||
<ScrollViewer VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical" Spacing="24">
|
||||
<!-- Remappings Section -->
|
||||
<StackPanel Orientation="Vertical" Visibility="{x:Bind RemappingList.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="RemappingsHeader"
|
||||
Margin="16,16,0,8"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<Rectangle
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<ListView
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="RemappingsList_ItemClick"
|
||||
ItemsSource="{x:Bind RemappingList}"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:Remapping">
|
||||
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
x:Uid="MapsToText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind RemappedKeys}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource RemappedKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="InText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<ToggleSwitch
|
||||
IsOn="{x:Bind IsActive}"
|
||||
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||
Toggled="ToggleSwitch_Toggled" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="DeleteMenuItem"
|
||||
Click="DeleteMapping_Click"
|
||||
Icon="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Text Section -->
|
||||
<StackPanel Orientation="Vertical" Visibility="{x:Bind TextMappings.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="TextMappingsHeader"
|
||||
Margin="16,16,0,8"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<Rectangle
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<ListView
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="TextMappingsList_ItemClick"
|
||||
ItemsSource="{x:Bind TextMappings}"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:TextMapping">
|
||||
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
x:Uid="InsertsText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl
|
||||
ActionType="Text"
|
||||
Label="{x:Bind Text}"
|
||||
Style="{StaticResource RemappedIconLabelControlStyle}" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="InText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<ToggleSwitch
|
||||
IsOn="{x:Bind IsActive}"
|
||||
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||
Toggled="ToggleSwitch_Toggled" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="DeleteMenuItem"
|
||||
Click="DeleteMapping_Click"
|
||||
Icon="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Programs Section -->
|
||||
<StackPanel Orientation="Vertical" Visibility="{x:Bind ProgramShortcuts.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="ProgramsHeader"
|
||||
Margin="16,16,0,8"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<Rectangle
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
|
||||
<ListView
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ProgramShortcutsList_ItemClick"
|
||||
ItemsSource="{x:Bind ProgramShortcuts}"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:ProgramShortcut">
|
||||
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip>
|
||||
<StackPanel Spacing="4">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock x:Uid="TooltipArguments" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Bind Args}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind StartInDirectory, Converter={StaticResource StringVisibilityConverter}}">
|
||||
<TextBlock x:Uid="TooltipStartIn" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Bind StartInDirectory}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock x:Uid="TooltipElevation" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Bind Elevation}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock x:Uid="TooltipIfRunning" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Bind IfRunningAction}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock x:Uid="TooltipWindow" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Bind Visibility}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ToolTip>
|
||||
</ToolTipService.ToolTip>
|
||||
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
x:Uid="OpensText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl
|
||||
ActionType="Program"
|
||||
Label="{x:Bind AppToRun}"
|
||||
Style="{StaticResource RemappedIconLabelControlStyle}" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="InText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<ToggleSwitch
|
||||
IsOn="{x:Bind IsActive}"
|
||||
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||
Toggled="ToggleSwitch_Toggled" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="DeleteMenuItem"
|
||||
Click="DeleteMapping_Click"
|
||||
Icon="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<!-- URLs Section -->
|
||||
<StackPanel Orientation="Vertical" Visibility="{x:Bind UrlShortcuts.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="UrlsHeader"
|
||||
Margin="16,16,0,8"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<Rectangle
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<ListView
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="UrlShortcutsList_ItemClick"
|
||||
ItemsSource="{x:Bind UrlShortcuts}"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:URLShortcut">
|
||||
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
x:Uid="OpensText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl
|
||||
ActionType="Url"
|
||||
Label="{x:Bind URL}"
|
||||
Style="{StaticResource RemappedIconLabelControlStyle}" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="InText"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<ToggleSwitch
|
||||
IsOn="{x:Bind IsActive}"
|
||||
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||
Toggled="ToggleSwitch_Toggled" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="DeleteMenuItem"
|
||||
Click="DeleteMapping_Click"
|
||||
Icon="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</tkcontrols:Case>
|
||||
</tkcontrols:SwitchPresenter>
|
||||
</Grid>
|
||||
|
||||
<!-- Content Dialog for new remapping -->
|
||||
<ContentDialog
|
||||
x:Name="RemappingDialog"
|
||||
x:Uid="RemappingDialog"
|
||||
Width="760"
|
||||
MinWidth="800"
|
||||
MinHeight="500"
|
||||
MaxWidth="900"
|
||||
DefaultButton="Primary"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<controls:UnifiedMappingControl x:Name="UnifiedMappingControl" Margin="0,16,0,0" />
|
||||
</ContentDialog>
|
||||
|
||||
<!-- Confirmation Dialog for delete -->
|
||||
<ContentDialog
|
||||
x:Name="DeleteConfirmationDialog"
|
||||
x:Uid="DeleteConfirmationDialog"
|
||||
DefaultButton="Primary">
|
||||
<TextBlock x:Uid="DeleteConfirmationDialogContent" TextWrapping="Wrap" />
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,897 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using KeyboardManagerEditorUI.Controls;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using KeyboardManagerEditorUI.Settings;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// A consolidated page that displays all mappings from Remappings, Text, Programs, and URLs pages.
|
||||
/// </summary>
|
||||
#pragma warning disable SA1124 // Do not use regions
|
||||
public sealed partial class MainPage : Page, IDisposable, INotifyPropertyChanged
|
||||
{
|
||||
private KeyboardMappingService? _mappingService;
|
||||
private bool _disposed;
|
||||
private bool _isEditMode;
|
||||
private EditingItem? _editingItem;
|
||||
private string _mappingState = "Empty";
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public string MappingState
|
||||
{
|
||||
get => _mappingState;
|
||||
private set
|
||||
{
|
||||
if (_mappingState != value)
|
||||
{
|
||||
_mappingState = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MappingState)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<Remapping> RemappingList { get; } = new();
|
||||
|
||||
public ObservableCollection<TextMapping> TextMappings { get; } = new();
|
||||
|
||||
public ObservableCollection<ProgramShortcut> ProgramShortcuts { get; } = new();
|
||||
|
||||
public ObservableCollection<URLShortcut> UrlShortcuts { get; } = new();
|
||||
|
||||
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||
|
||||
private sealed class EditingItem
|
||||
{
|
||||
public enum ItemType
|
||||
{
|
||||
Remapping,
|
||||
TextMapping,
|
||||
ProgramShortcut,
|
||||
UrlShortcut,
|
||||
}
|
||||
|
||||
public ItemType Type { get; set; }
|
||||
|
||||
public object Item { get; set; } = null!;
|
||||
|
||||
public List<string> OriginalTriggerKeys { get; set; } = new();
|
||||
|
||||
public string? AppName { get; set; }
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
}
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
try
|
||||
{
|
||||
_mappingService = new KeyboardMappingService();
|
||||
LoadAllMappings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to initialize KeyboardMappingService in MainPage page: " + ex.Message);
|
||||
}
|
||||
|
||||
Unloaded += All_Unloaded;
|
||||
}
|
||||
|
||||
private void All_Unloaded(object sender, RoutedEventArgs e) => Dispose();
|
||||
|
||||
#region Dialog Show Methods
|
||||
|
||||
private async void NewRemappingBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isEditMode = false;
|
||||
_editingItem = null;
|
||||
UnifiedMappingControl.Reset();
|
||||
RemappingDialog.Title = "New remapping";
|
||||
await ShowRemappingDialog();
|
||||
}
|
||||
|
||||
private async void RemappingsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is not Remapping remapping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isEditMode = true;
|
||||
_editingItem = new EditingItem
|
||||
{
|
||||
Type = EditingItem.ItemType.Remapping,
|
||||
Item = remapping,
|
||||
OriginalTriggerKeys = remapping.Shortcut.ToList(),
|
||||
AppName = remapping.AppName,
|
||||
IsAllApps = remapping.IsAllApps,
|
||||
};
|
||||
|
||||
UnifiedMappingControl.Reset();
|
||||
UnifiedMappingControl.SetTriggerKeys(remapping.Shortcut.ToList());
|
||||
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.KeyOrShortcut);
|
||||
UnifiedMappingControl.SetActionKeys(remapping.RemappedKeys.ToList());
|
||||
UnifiedMappingControl.SetAppSpecific(!remapping.IsAllApps, remapping.AppName);
|
||||
RemappingDialog.Title = "Edit remapping";
|
||||
await ShowRemappingDialog();
|
||||
}
|
||||
|
||||
private async void TextMappingsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is not TextMapping textMapping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isEditMode = true;
|
||||
_editingItem = new EditingItem
|
||||
{
|
||||
Type = EditingItem.ItemType.TextMapping,
|
||||
Item = textMapping,
|
||||
OriginalTriggerKeys = textMapping.Shortcut.ToList(),
|
||||
AppName = textMapping.AppName,
|
||||
IsAllApps = textMapping.IsAllApps,
|
||||
};
|
||||
|
||||
UnifiedMappingControl.Reset();
|
||||
UnifiedMappingControl.SetTriggerKeys(textMapping.Shortcut.ToList());
|
||||
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.Text);
|
||||
UnifiedMappingControl.SetTextContent(textMapping.Text);
|
||||
UnifiedMappingControl.SetAppSpecific(!textMapping.IsAllApps, textMapping.AppName);
|
||||
RemappingDialog.Title = "Edit remapping";
|
||||
await ShowRemappingDialog();
|
||||
}
|
||||
|
||||
private async void ProgramShortcutsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is not ProgramShortcut programShortcut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isEditMode = true;
|
||||
_editingItem = new EditingItem
|
||||
{
|
||||
Type = EditingItem.ItemType.ProgramShortcut,
|
||||
Item = programShortcut,
|
||||
OriginalTriggerKeys = programShortcut.Shortcut.ToList(),
|
||||
AppName = programShortcut.AppName,
|
||||
IsAllApps = programShortcut.IsAllApps,
|
||||
};
|
||||
|
||||
UnifiedMappingControl.Reset();
|
||||
UnifiedMappingControl.SetTriggerKeys(programShortcut.Shortcut.ToList());
|
||||
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.OpenApp);
|
||||
UnifiedMappingControl.SetProgramPath(programShortcut.AppToRun);
|
||||
UnifiedMappingControl.SetProgramArgs(programShortcut.Args);
|
||||
|
||||
if (!string.IsNullOrEmpty(programShortcut.Id) &&
|
||||
SettingsManager.EditorSettings.ShortcutSettingsDictionary.TryGetValue(programShortcut.Id, out var settings))
|
||||
{
|
||||
var mapping = settings.Shortcut;
|
||||
UnifiedMappingControl.SetStartInDirectory(mapping.StartInDirectory);
|
||||
UnifiedMappingControl.SetElevationLevel(mapping.Elevation);
|
||||
UnifiedMappingControl.SetVisibility(mapping.Visibility);
|
||||
UnifiedMappingControl.SetIfRunningAction(mapping.IfRunningAction);
|
||||
}
|
||||
|
||||
UnifiedMappingControl.SetAppSpecific(!programShortcut.IsAllApps, programShortcut.AppName);
|
||||
RemappingDialog.Title = "Edit remapping";
|
||||
await ShowRemappingDialog();
|
||||
}
|
||||
|
||||
private async void UrlShortcutsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is not URLShortcut urlShortcut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isEditMode = true;
|
||||
_editingItem = new EditingItem
|
||||
{
|
||||
Type = EditingItem.ItemType.UrlShortcut,
|
||||
Item = urlShortcut,
|
||||
OriginalTriggerKeys = urlShortcut.Shortcut.ToList(),
|
||||
AppName = urlShortcut.AppName,
|
||||
IsAllApps = urlShortcut.IsAllApps,
|
||||
};
|
||||
|
||||
UnifiedMappingControl.Reset();
|
||||
UnifiedMappingControl.SetTriggerKeys(urlShortcut.Shortcut.ToList());
|
||||
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.OpenUrl);
|
||||
UnifiedMappingControl.SetUrl(urlShortcut.URL);
|
||||
UnifiedMappingControl.SetAppSpecific(!urlShortcut.IsAllApps, urlShortcut.AppName);
|
||||
RemappingDialog.Title = "Edit remapping";
|
||||
await ShowRemappingDialog();
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ShowRemappingDialog()
|
||||
{
|
||||
RemappingDialog.PrimaryButtonClick += RemappingDialog_PrimaryButtonClick;
|
||||
UnifiedMappingControl.ValidationStateChanged += UnifiedMappingControl_ValidationStateChanged;
|
||||
RemappingDialog.IsPrimaryButtonEnabled = UnifiedMappingControl.IsInputComplete();
|
||||
|
||||
await RemappingDialog.ShowAsync();
|
||||
|
||||
RemappingDialog.PrimaryButtonClick -= RemappingDialog_PrimaryButtonClick;
|
||||
UnifiedMappingControl.ValidationStateChanged -= UnifiedMappingControl_ValidationStateChanged;
|
||||
_isEditMode = false;
|
||||
_editingItem = null;
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
|
||||
private void UnifiedMappingControl_ValidationStateChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (!UnifiedMappingControl.IsInputComplete())
|
||||
{
|
||||
RemappingDialog.IsPrimaryButtonEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mappingService != null)
|
||||
{
|
||||
List<string> triggerKeys = UnifiedMappingControl.GetTriggerKeys();
|
||||
if (triggerKeys?.Count > 0)
|
||||
{
|
||||
ValidationErrorType error = ValidateMapping(UnifiedMappingControl.CurrentActionType, triggerKeys);
|
||||
if (error != ValidationErrorType.NoError)
|
||||
{
|
||||
UnifiedMappingControl.ShowValidationErrorFromType(error);
|
||||
RemappingDialog.IsPrimaryButtonEnabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnifiedMappingControl.HideValidationMessage();
|
||||
RemappingDialog.IsPrimaryButtonEnabled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save Logic
|
||||
|
||||
private void RemappingDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
UnifiedMappingControl.HideValidationMessage();
|
||||
|
||||
if (_mappingService == null)
|
||||
{
|
||||
Logger.LogError("Mapping service is null, cannot save mapping");
|
||||
UnifiedMappingControl.ShowValidationError("Error", "Mapping service is not available.");
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<string> triggerKeys = UnifiedMappingControl.GetTriggerKeys();
|
||||
|
||||
if (triggerKeys == null || triggerKeys.Count == 0)
|
||||
{
|
||||
UnifiedMappingControl.ShowValidationError("Missing Original Keys", "Please enter at least one original key to create a remapping.");
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationErrorType validationError = ValidateMapping(UnifiedMappingControl.CurrentActionType, triggerKeys);
|
||||
if (validationError != ValidationErrorType.NoError)
|
||||
{
|
||||
UnifiedMappingControl.ShowValidationErrorFromType(validationError);
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isEditMode && _editingItem != null)
|
||||
{
|
||||
DeleteExistingMapping();
|
||||
}
|
||||
|
||||
bool saved = UnifiedMappingControl.CurrentActionType switch
|
||||
{
|
||||
UnifiedMappingControl.ActionType.KeyOrShortcut => SaveKeyOrShortcutMapping(triggerKeys),
|
||||
UnifiedMappingControl.ActionType.Text => SaveTextMapping(triggerKeys),
|
||||
UnifiedMappingControl.ActionType.OpenUrl => SaveUrlMapping(triggerKeys),
|
||||
UnifiedMappingControl.ActionType.OpenApp => SaveProgramMapping(triggerKeys),
|
||||
UnifiedMappingControl.ActionType.MouseClick => throw new NotImplementedException("Mouse click remapping is not yet supported."),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (saved)
|
||||
{
|
||||
LoadAllMappings();
|
||||
}
|
||||
else
|
||||
{
|
||||
UnifiedMappingControl.ShowValidationError("Save Failed", "Failed to save the remapping. Please try again.");
|
||||
args.Cancel = true;
|
||||
}
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
UnifiedMappingControl.ShowValidationError("Not Implemented", ex.Message);
|
||||
args.Cancel = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error saving mapping: " + ex.Message);
|
||||
UnifiedMappingControl.ShowValidationError("Error", "An error occurred while saving: " + ex.Message);
|
||||
args.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private ValidationErrorType ValidateMapping(UnifiedMappingControl.ActionType actionType, List<string> triggerKeys)
|
||||
{
|
||||
bool isAppSpecific = UnifiedMappingControl.GetIsAppSpecific();
|
||||
string appName = UnifiedMappingControl.GetAppName();
|
||||
Remapping? editingRemapping = _isEditMode && _editingItem?.Item is Remapping r ? r : null;
|
||||
|
||||
return actionType switch
|
||||
{
|
||||
UnifiedMappingControl.ActionType.KeyOrShortcut => ValidationHelper.ValidateKeyMapping(
|
||||
triggerKeys, UnifiedMappingControl.GetActionKeys(), isAppSpecific, appName, _mappingService!, _isEditMode, editingRemapping),
|
||||
UnifiedMappingControl.ActionType.Text => ValidationHelper.ValidateTextMapping(
|
||||
triggerKeys, UnifiedMappingControl.GetTextContent(), isAppSpecific, appName, _mappingService!, _isEditMode),
|
||||
UnifiedMappingControl.ActionType.OpenUrl => ValidationHelper.ValidateUrlMapping(
|
||||
triggerKeys, UnifiedMappingControl.GetUrl(), isAppSpecific, appName, _mappingService!, _isEditMode),
|
||||
UnifiedMappingControl.ActionType.OpenApp => ValidationHelper.ValidateAppMapping(
|
||||
triggerKeys, UnifiedMappingControl.GetProgramPath(), isAppSpecific, appName, _mappingService!, _isEditMode),
|
||||
_ => ValidationErrorType.NoError,
|
||||
};
|
||||
}
|
||||
|
||||
private void DeleteExistingMapping()
|
||||
{
|
||||
if (_editingItem == null || _mappingService == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (_editingItem.Type)
|
||||
{
|
||||
case EditingItem.ItemType.Remapping when _editingItem.Item is Remapping remapping:
|
||||
RemappingHelper.DeleteRemapping(_mappingService, remapping);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_editingItem.Item is IToggleableShortcut shortcut)
|
||||
{
|
||||
DeleteShortcutMapping(_editingItem.OriginalTriggerKeys, _editingItem.AppName ?? string.Empty);
|
||||
if (!string.IsNullOrEmpty(shortcut.Id))
|
||||
{
|
||||
SettingsManager.RemoveShortcutKeyMappingFromSettings(shortcut.Id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error deleting existing mapping: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteShortcutMapping(List<string> originalKeys, string targetApp = "")
|
||||
{
|
||||
bool deleted = originalKeys.Count == 1
|
||||
? DeleteSingleKeyToTextMapping(originalKeys[0])
|
||||
: DeleteMultiKeyMapping(originalKeys, targetApp);
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
_mappingService!.SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private bool DeleteMultiKeyMapping(List<string> originalKeys, string targetApp = "")
|
||||
{
|
||||
string originalKeysString = string.Join(";", originalKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
return _mappingService!.DeleteShortcutMapping(originalKeysString, targetApp);
|
||||
}
|
||||
|
||||
private bool SaveKeyOrShortcutMapping(List<string> triggerKeys)
|
||||
{
|
||||
List<string> actionKeys = UnifiedMappingControl.GetActionKeys();
|
||||
if (actionKeys == null || actionKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return RemappingHelper.SaveMapping(
|
||||
_mappingService!,
|
||||
triggerKeys,
|
||||
actionKeys,
|
||||
UnifiedMappingControl.GetIsAppSpecific(),
|
||||
UnifiedMappingControl.GetAppName());
|
||||
}
|
||||
|
||||
private bool SaveTextMapping(List<string> triggerKeys)
|
||||
{
|
||||
string textContent = UnifiedMappingControl.GetTextContent();
|
||||
bool isAppSpecific = UnifiedMappingControl.GetIsAppSpecific();
|
||||
string appName = UnifiedMappingControl.GetAppName();
|
||||
|
||||
if (string.IsNullOrEmpty(textContent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return triggerKeys.Count == 1
|
||||
? SaveSingleKeyToTextMapping(triggerKeys[0], textContent, isAppSpecific, appName)
|
||||
: SaveShortcutToTextMapping(triggerKeys, textContent, isAppSpecific, appName);
|
||||
}
|
||||
|
||||
private bool SaveSingleKeyToTextMapping(string keyName, string textContent, bool isAppSpecific, string appName)
|
||||
{
|
||||
int originalKey = _mappingService!.GetKeyCodeFromName(keyName);
|
||||
if (originalKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapText,
|
||||
OriginalKeys = originalKey.ToString(CultureInfo.InvariantCulture),
|
||||
TargetKeys = textContent,
|
||||
TargetText = textContent,
|
||||
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||
};
|
||||
|
||||
bool saved = _mappingService.AddSingleKeyToTextMapping(originalKey, textContent);
|
||||
if (saved)
|
||||
{
|
||||
_mappingService.SaveSettings();
|
||||
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
private bool SaveShortcutToTextMapping(List<string> triggerKeys, string textContent, bool isAppSpecific, string appName)
|
||||
{
|
||||
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapText,
|
||||
OriginalKeys = originalKeysString,
|
||||
TargetKeys = textContent,
|
||||
TargetText = textContent,
|
||||
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||
};
|
||||
|
||||
bool saved = isAppSpecific && !string.IsNullOrEmpty(appName)
|
||||
? _mappingService!.AddShortcutMapping(originalKeysString, textContent, appName, ShortcutOperationType.RemapText)
|
||||
: _mappingService!.AddShortcutMapping(originalKeysString, textContent, operationType: ShortcutOperationType.RemapText);
|
||||
|
||||
if (saved)
|
||||
{
|
||||
_mappingService.SaveSettings();
|
||||
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
private bool SaveUrlMapping(List<string> triggerKeys)
|
||||
{
|
||||
string url = UnifiedMappingControl.GetUrl();
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.OpenUri,
|
||||
OriginalKeys = originalKeysString,
|
||||
TargetKeys = originalKeysString,
|
||||
UriToOpen = url,
|
||||
TargetApp = UnifiedMappingControl.GetIsAppSpecific() ? UnifiedMappingControl.GetAppName() : string.Empty,
|
||||
};
|
||||
|
||||
bool saved = _mappingService!.AddShortcutMapping(shortcutKeyMapping);
|
||||
if (saved)
|
||||
{
|
||||
_mappingService.SaveSettings();
|
||||
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
private bool SaveProgramMapping(List<string> triggerKeys)
|
||||
{
|
||||
string programPath = UnifiedMappingControl.GetProgramPath();
|
||||
if (string.IsNullOrEmpty(programPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RunProgram,
|
||||
OriginalKeys = originalKeysString,
|
||||
TargetKeys = originalKeysString,
|
||||
ProgramPath = programPath,
|
||||
ProgramArgs = UnifiedMappingControl.GetProgramArgs(),
|
||||
StartInDirectory = UnifiedMappingControl.GetStartInDirectory(),
|
||||
IfRunningAction = UnifiedMappingControl.GetIfRunningAction(),
|
||||
Visibility = UnifiedMappingControl.GetVisibility(),
|
||||
Elevation = UnifiedMappingControl.GetElevationLevel(),
|
||||
TargetApp = UnifiedMappingControl.GetIsAppSpecific() ? UnifiedMappingControl.GetAppName() : string.Empty,
|
||||
};
|
||||
|
||||
bool saved = _mappingService!.AddShortcutMapping(shortcutKeyMapping);
|
||||
if (saved)
|
||||
{
|
||||
_mappingService.SaveSettings();
|
||||
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete Handlers
|
||||
|
||||
private async void DeleteMapping_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not MenuFlyoutItem menuFlyoutItem || _mappingService == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await DeleteConfirmationDialog.ShowAsync() != ContentDialogResult.Primary)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (menuFlyoutItem.Tag)
|
||||
{
|
||||
case Remapping remapping:
|
||||
HandleRemappingDelete(remapping);
|
||||
UpdateHasAnyMappings();
|
||||
break;
|
||||
|
||||
case IToggleableShortcut shortcut:
|
||||
HandleShortcutDelete(shortcut);
|
||||
LoadAllMappings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error deleting mapping: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRemappingDelete(Remapping remapping)
|
||||
{
|
||||
if (!remapping.IsActive)
|
||||
{
|
||||
SettingsManager.RemoveShortcutKeyMappingFromSettings(remapping.Id);
|
||||
LoadRemappings();
|
||||
}
|
||||
else if (RemappingHelper.DeleteRemapping(_mappingService!, remapping))
|
||||
{
|
||||
LoadRemappings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"Failed to delete remapping: {string.Join("+", remapping.Shortcut)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleShortcutDelete(IToggleableShortcut shortcut)
|
||||
{
|
||||
bool deleted = shortcut.Shortcut.Count == 1
|
||||
? DeleteSingleKeyToTextMapping(shortcut.Shortcut[0]) // Remapping has its own handler, single key will always be text mapping
|
||||
: DeleteMultiKeyShortcut(shortcut);
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
_mappingService!.SaveSettings();
|
||||
}
|
||||
|
||||
SettingsManager.RemoveShortcutKeyMappingFromSettings(shortcut.Id);
|
||||
}
|
||||
|
||||
private bool DeleteMultiKeyShortcut(IToggleableShortcut shortcut)
|
||||
{
|
||||
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService!.GetKeyCodeFromName(k)));
|
||||
return _mappingService!.DeleteShortcutMapping(originalKeys, shortcut.AppName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Toggle Switch Handlers
|
||||
|
||||
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not ToggleSwitch toggleSwitch || toggleSwitch.DataContext is not IToggleableShortcut shortcut || _mappingService == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (toggleSwitch.IsOn)
|
||||
{
|
||||
EnableShortcut(shortcut);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisableShortcut(shortcut);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error toggling shortcut active state: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableShortcut(IToggleableShortcut shortcut)
|
||||
{
|
||||
if (shortcut is Remapping remapping)
|
||||
{
|
||||
RemappingHelper.SaveMapping(_mappingService!, remapping.Shortcut, remapping.RemappedKeys, !remapping.IsAllApps, remapping.AppName, false);
|
||||
shortcut.IsActive = true;
|
||||
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
ShortcutKeyMapping shortcutKeyMapping = SettingsManager.EditorSettings.ShortcutSettingsDictionary[shortcut.Id].Shortcut;
|
||||
bool saved = shortcut.Shortcut.Count == 1
|
||||
? _mappingService!.AddSingleKeyToTextMapping(_mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]), shortcutKeyMapping.TargetText)
|
||||
: shortcutKeyMapping.OperationType == ShortcutOperationType.RemapText
|
||||
? _mappingService!.AddShortcutMapping(shortcutKeyMapping.OriginalKeys, shortcutKeyMapping.TargetText, operationType: ShortcutOperationType.RemapText)
|
||||
: _mappingService!.AddShortcutMapping(shortcutKeyMapping);
|
||||
|
||||
if (saved)
|
||||
{
|
||||
shortcut.IsActive = true;
|
||||
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||
_mappingService.SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisableShortcut(IToggleableShortcut shortcut)
|
||||
{
|
||||
if (shortcut is Remapping remapping)
|
||||
{
|
||||
shortcut.IsActive = false;
|
||||
RemappingHelper.DeleteRemapping(_mappingService!, remapping, false);
|
||||
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
bool deleted = shortcut.Shortcut.Count == 1
|
||||
? DeleteSingleKeyToTextMapping(shortcut.Shortcut[0])
|
||||
: DeleteMultiKeyMapping(shortcut.Shortcut, shortcut.AppName);
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
shortcut.IsActive = false;
|
||||
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||
_mappingService!.SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private bool DeleteSingleKeyToTextMapping(string keyName)
|
||||
{
|
||||
int originalKey = _mappingService!.GetKeyCodeFromName(keyName);
|
||||
return originalKey != 0 && _mappingService.DeleteSingleKeyToTextMapping(originalKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Load Methods
|
||||
|
||||
private void LoadAllMappings()
|
||||
{
|
||||
LoadRemappings();
|
||||
LoadTextMappings();
|
||||
LoadProgramShortcuts();
|
||||
LoadUrlShortcuts();
|
||||
UpdateHasAnyMappings();
|
||||
}
|
||||
|
||||
private void UpdateHasAnyMappings()
|
||||
{
|
||||
bool hasAny = RemappingList.Count > 0 || TextMappings.Count > 0 || ProgramShortcuts.Count > 0 || UrlShortcuts.Count > 0;
|
||||
MappingState = hasAny ? "HasMappings" : "Empty";
|
||||
}
|
||||
|
||||
private void LoadRemappings()
|
||||
{
|
||||
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.RemapShortcut, out var remapShortcutIds);
|
||||
|
||||
if (_mappingService == null || remapShortcutIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemappingList.Clear();
|
||||
|
||||
foreach (var id in remapShortcutIds)
|
||||
{
|
||||
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||
var remappedKeyNames = ParseKeyCodes(mapping.TargetKeys);
|
||||
|
||||
RemappingList.Add(new Remapping
|
||||
{
|
||||
Shortcut = originalKeyNames,
|
||||
RemappedKeys = remappedKeyNames,
|
||||
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||
AppName = mapping.TargetApp ?? string.Empty,
|
||||
Id = shortcutSettings.Id,
|
||||
IsActive = shortcutSettings.IsActive,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadTextMappings()
|
||||
{
|
||||
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.RemapText, out var remapShortcutIds);
|
||||
|
||||
if (_mappingService == null || remapShortcutIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TextMappings.Clear();
|
||||
|
||||
foreach (var id in remapShortcutIds)
|
||||
{
|
||||
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||
|
||||
TextMappings.Add(new TextMapping
|
||||
{
|
||||
Shortcut = originalKeyNames,
|
||||
Text = mapping.TargetText,
|
||||
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||
AppName = mapping.TargetApp ?? string.Empty,
|
||||
Id = shortcutSettings.Id,
|
||||
IsActive = shortcutSettings.IsActive,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadProgramShortcuts()
|
||||
{
|
||||
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.RunProgram, out var remapShortcutIds);
|
||||
|
||||
if (_mappingService == null || remapShortcutIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramShortcuts.Clear();
|
||||
|
||||
foreach (var id in remapShortcutIds)
|
||||
{
|
||||
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||
|
||||
ProgramShortcuts.Add(new ProgramShortcut
|
||||
{
|
||||
Shortcut = originalKeyNames,
|
||||
AppToRun = mapping.ProgramPath,
|
||||
Args = mapping.ProgramArgs,
|
||||
IsActive = shortcutSettings.IsActive,
|
||||
Id = shortcutSettings.Id,
|
||||
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||
AppName = mapping.TargetApp ?? string.Empty,
|
||||
StartInDirectory = mapping.StartInDirectory,
|
||||
Elevation = mapping.Elevation.ToString(),
|
||||
IfRunningAction = mapping.IfRunningAction.ToString(),
|
||||
Visibility = mapping.Visibility.ToString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadUrlShortcuts()
|
||||
{
|
||||
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.OpenUri, out var remapShortcutIds);
|
||||
|
||||
if (_mappingService == null || remapShortcutIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UrlShortcuts.Clear();
|
||||
|
||||
foreach (var id in remapShortcutIds)
|
||||
{
|
||||
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||
|
||||
UrlShortcuts.Add(new URLShortcut
|
||||
{
|
||||
Shortcut = originalKeyNames,
|
||||
URL = mapping.UriToOpen,
|
||||
Id = shortcutSettings.Id,
|
||||
IsActive = shortcutSettings.IsActive,
|
||||
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||
AppName = mapping.TargetApp ?? string.Empty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> ParseKeyCodes(string keyCodesString)
|
||||
{
|
||||
return keyCodesString.Split(';')
|
||||
.Where(keyCode => int.TryParse(keyCode, out int code))
|
||||
.Select(keyCode => _mappingService!.GetKeyDisplayName(int.Parse(keyCode, CultureInfo.InvariantCulture)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_mappingService?.Dispose();
|
||||
_mappingService = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#pragma warning restore SA1124 // Do not use regions
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 KeyboardManagerEditorUI.Interop;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Settings
|
||||
{
|
||||
public class EditorSettings
|
||||
{
|
||||
public Dictionary<string, ShortcutSettings> ShortcutSettingsDictionary { get; set; } = new Dictionary<string, ShortcutSettings>();
|
||||
|
||||
public Dictionary<string, List<string>> ProfileDictionary { get; set; } = new Dictionary<string, List<string>>();
|
||||
|
||||
public Dictionary<ShortcutOperationType, List<string>> ShortcutsByOperationType { get; set; } = new Dictionary<ShortcutOperationType, List<string>>();
|
||||
|
||||
public string ActiveProfile { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Settings
|
||||
{
|
||||
internal static class SettingsManager
|
||||
{
|
||||
private static readonly string _settingsDirectory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Microsoft",
|
||||
"PowerToys",
|
||||
"Keyboard Manager");
|
||||
|
||||
private static readonly string _settingsFilePath = Path.Combine(_settingsDirectory, "editorSettings.json");
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { WriteIndented = true };
|
||||
|
||||
private static readonly KeyboardMappingService _mappingService = new KeyboardMappingService();
|
||||
|
||||
public static EditorSettings EditorSettings { get; set; }
|
||||
|
||||
static SettingsManager()
|
||||
{
|
||||
EditorSettings = LoadSettings();
|
||||
}
|
||||
|
||||
public static EditorSettings LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_settingsFilePath))
|
||||
{
|
||||
EditorSettings createdSettings = CreateSettingsFromKeyboardManagerService();
|
||||
WriteSettings(createdSettings);
|
||||
return createdSettings;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(_settingsFilePath);
|
||||
return JsonSerializer.Deserialize<EditorSettings>(json, _jsonOptions) ?? new EditorSettings();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new EditorSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool WriteSettings(EditorSettings editorSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_settingsDirectory);
|
||||
string json = JsonSerializer.Serialize(editorSettings, _jsonOptions);
|
||||
File.WriteAllText(_settingsFilePath, json);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool WriteSettings() => WriteSettings(EditorSettings);
|
||||
|
||||
private static EditorSettings CreateSettingsFromKeyboardManagerService()
|
||||
{
|
||||
EditorSettings settings = new EditorSettings();
|
||||
|
||||
// Process all shortcut mappings (RunProgram, OpenUri, RemapShortcut, RemapText)
|
||||
foreach (ShortcutKeyMapping mapping in _mappingService.GetShortcutMappings())
|
||||
{
|
||||
AddShortcutMapping(settings, mapping);
|
||||
}
|
||||
|
||||
// Process single key to key mappings
|
||||
foreach (var mapping in _mappingService.GetSingleKeyMappings())
|
||||
{
|
||||
var shortcutMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapShortcut,
|
||||
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||
TargetKeys = mapping.TargetKey,
|
||||
};
|
||||
AddShortcutMapping(settings, shortcutMapping);
|
||||
}
|
||||
|
||||
// Process single key to text mappings
|
||||
foreach (var mapping in _mappingService.GetKeyToTextMappings())
|
||||
{
|
||||
var shortcutMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapText,
|
||||
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||
TargetKeys = mapping.TargetText,
|
||||
TargetText = mapping.TargetText,
|
||||
};
|
||||
AddShortcutMapping(settings, shortcutMapping);
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public static void CorrelateServiceAndEditorMappings()
|
||||
{
|
||||
bool shortcutSettingsChanged = false;
|
||||
|
||||
// Process all shortcut mappings
|
||||
foreach (ShortcutKeyMapping mapping in _mappingService.GetShortcutMappings())
|
||||
{
|
||||
if (!EditorSettings.ShortcutSettingsDictionary.Values.Any(s => s.Shortcut.OriginalKeys == mapping.OriginalKeys))
|
||||
{
|
||||
AddShortcutMapping(EditorSettings, mapping);
|
||||
shortcutSettingsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process single key to key mappings
|
||||
foreach (var mapping in _mappingService.GetSingleKeyMappings())
|
||||
{
|
||||
var shortcutMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapShortcut,
|
||||
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||
TargetKeys = mapping.TargetKey,
|
||||
};
|
||||
|
||||
if (!MappingExists(shortcutMapping))
|
||||
{
|
||||
AddShortcutMapping(EditorSettings, shortcutMapping);
|
||||
shortcutSettingsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process single key to text mappings
|
||||
foreach (var mapping in _mappingService.GetKeyToTextMappings())
|
||||
{
|
||||
var shortcutMapping = new ShortcutKeyMapping
|
||||
{
|
||||
OperationType = ShortcutOperationType.RemapText,
|
||||
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||
TargetKeys = mapping.TargetText,
|
||||
TargetText = mapping.TargetText,
|
||||
};
|
||||
|
||||
if (!EditorSettings.ShortcutSettingsDictionary.Values.Any(s => s.Shortcut.OriginalKeys == shortcutMapping.OriginalKeys))
|
||||
{
|
||||
AddShortcutMapping(EditorSettings, shortcutMapping);
|
||||
shortcutSettingsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark inactive mappings
|
||||
var singleKeyMappings = _mappingService.GetSingleKeyMappings();
|
||||
var keyToTextMappings = _mappingService.GetKeyToTextMappings();
|
||||
var shortcutKeyMappings = _mappingService.GetShortcutMappings();
|
||||
|
||||
foreach (ShortcutSettings shortcutSettings in EditorSettings.ShortcutSettingsDictionary.Values.ToList())
|
||||
{
|
||||
bool foundInService = IsMappingActiveInService(
|
||||
shortcutSettings,
|
||||
keyToTextMappings,
|
||||
singleKeyMappings,
|
||||
shortcutKeyMappings);
|
||||
|
||||
if (!foundInService)
|
||||
{
|
||||
shortcutSettingsChanged = true;
|
||||
shortcutSettings.IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcutSettingsChanged)
|
||||
{
|
||||
WriteSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddShortcutKeyMappingToSettings(ShortcutKeyMapping shortcutKeyMapping)
|
||||
{
|
||||
AddShortcutMapping(EditorSettings, shortcutKeyMapping);
|
||||
WriteSettings();
|
||||
}
|
||||
|
||||
public static void RemoveShortcutKeyMappingFromSettings(string guid)
|
||||
{
|
||||
ShortcutOperationType operationType = EditorSettings.ShortcutSettingsDictionary[guid].Shortcut.OperationType;
|
||||
EditorSettings.ShortcutSettingsDictionary.Remove(guid);
|
||||
|
||||
if (EditorSettings.ShortcutsByOperationType.TryGetValue(operationType, out var value))
|
||||
{
|
||||
value.Remove(guid);
|
||||
}
|
||||
|
||||
WriteSettings();
|
||||
}
|
||||
|
||||
public static void ToggleShortcutKeyMappingActiveState(string guid)
|
||||
{
|
||||
if (EditorSettings.ShortcutSettingsDictionary.TryGetValue(guid, out ShortcutSettings? shortcutSettings))
|
||||
{
|
||||
shortcutSettings.IsActive = !shortcutSettings.IsActive;
|
||||
WriteSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddShortcutMapping(EditorSettings settings, ShortcutKeyMapping mapping)
|
||||
{
|
||||
string guid = Guid.NewGuid().ToString();
|
||||
var shortcutSettings = new ShortcutSettings
|
||||
{
|
||||
Id = guid,
|
||||
Shortcut = mapping,
|
||||
IsActive = true,
|
||||
};
|
||||
|
||||
settings.ShortcutSettingsDictionary[guid] = shortcutSettings;
|
||||
|
||||
if (!settings.ShortcutsByOperationType.TryGetValue(mapping.OperationType, out System.Collections.Generic.List<string>? value))
|
||||
{
|
||||
value = new System.Collections.Generic.List<string>();
|
||||
settings.ShortcutsByOperationType[mapping.OperationType] = value;
|
||||
}
|
||||
|
||||
value.Add(guid);
|
||||
}
|
||||
|
||||
private static bool MappingExists(ShortcutKeyMapping mapping)
|
||||
{
|
||||
return EditorSettings.ShortcutSettingsDictionary.Values.Any(s =>
|
||||
s.Shortcut.OperationType == mapping.OperationType &&
|
||||
s.Shortcut.OriginalKeys == mapping.OriginalKeys &&
|
||||
s.Shortcut.TargetKeys == mapping.TargetKeys);
|
||||
}
|
||||
|
||||
private static bool IsMappingActiveInService(
|
||||
ShortcutSettings shortcutSettings,
|
||||
List<KeyToTextMapping> keyToTextMappings,
|
||||
List<KeyMapping> singleKeyMappings,
|
||||
List<ShortcutKeyMapping> shortcutKeyMappings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutSettings.Shortcut.OriginalKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isSingleKey = shortcutSettings.Shortcut.OriginalKeys.Split(';').Length == 1;
|
||||
|
||||
if (isSingleKey && int.TryParse(shortcutSettings.Shortcut.OriginalKeys, out int keyCode))
|
||||
{
|
||||
if (shortcutSettings.Shortcut.OperationType == ShortcutOperationType.RemapText)
|
||||
{
|
||||
return keyToTextMappings.Any(m =>
|
||||
m.OriginalKey == keyCode &&
|
||||
m.TargetText == shortcutSettings.Shortcut.TargetText);
|
||||
}
|
||||
else if (shortcutSettings.Shortcut.OperationType == ShortcutOperationType.RemapShortcut)
|
||||
{
|
||||
return singleKeyMappings.Any(m =>
|
||||
m.OriginalKey == keyCode &&
|
||||
m.TargetKey == shortcutSettings.Shortcut.TargetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
return shortcutKeyMappings.Any(m => m.OriginalKeys == shortcutSettings.Shortcut.OriginalKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Settings
|
||||
{
|
||||
public class ShortcutSettings
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public ShortcutKeyMapping Shortcut { get; set; } = new ShortcutKeyMapping();
|
||||
|
||||
public List<string> Profiles { get; set; } = new List<string>();
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="NewRemappingBtn_Text.Text" xml:space="preserve">
|
||||
<value>Add new remapping</value>
|
||||
</data>
|
||||
<data name="EmptyStateTitle.Text" xml:space="preserve">
|
||||
<value>Nothing mapped yet</value>
|
||||
</data>
|
||||
<data name="EmptyStateDescription.Text" xml:space="preserve">
|
||||
<value>Create a key or shortcut remapping to customize how your keyboard works.</value>
|
||||
</data>
|
||||
<data name="RemappingsHeader.Text" xml:space="preserve">
|
||||
<value>Keys and shortcuts</value>
|
||||
</data>
|
||||
<data name="TextMappingsHeader.Text" xml:space="preserve">
|
||||
<value>Text</value>
|
||||
</data>
|
||||
<data name="ProgramsHeader.Text" xml:space="preserve">
|
||||
<value>Programs</value>
|
||||
</data>
|
||||
<data name="UrlsHeader.Text" xml:space="preserve">
|
||||
<value>Urls</value>
|
||||
</data>
|
||||
<data name="MapsToText.Text" xml:space="preserve">
|
||||
<value>maps to</value>
|
||||
</data>
|
||||
<data name="InsertsText.Text" xml:space="preserve">
|
||||
<value>inserts</value>
|
||||
</data>
|
||||
<data name="OpensText.Text" xml:space="preserve">
|
||||
<value>opens</value>
|
||||
</data>
|
||||
<data name="InText.Text" xml:space="preserve">
|
||||
<value>in</value>
|
||||
</data>
|
||||
<data name="DeleteMenuItem.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="TooltipArguments.Text" xml:space="preserve">
|
||||
<value>Arguments:</value>
|
||||
</data>
|
||||
<data name="TooltipStartIn.Text" xml:space="preserve">
|
||||
<value>Start in:</value>
|
||||
</data>
|
||||
<data name="TooltipElevation.Text" xml:space="preserve">
|
||||
<value>Elevation:</value>
|
||||
</data>
|
||||
<data name="TooltipIfRunning.Text" xml:space="preserve">
|
||||
<value>If running:</value>
|
||||
</data>
|
||||
<data name="TooltipWindow.Text" xml:space="preserve">
|
||||
<value>Window:</value>
|
||||
</data>
|
||||
<data name="RemappingDialog.Title" xml:space="preserve">
|
||||
<value>Add new remapping</value>
|
||||
</data>
|
||||
<data name="RemappingDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="RemappingDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="DeleteConfirmationDialog.Title" xml:space="preserve">
|
||||
<value>Are you sure?</value>
|
||||
</data>
|
||||
<data name="DeleteConfirmationDialogContent.Text" xml:space="preserve">
|
||||
<value>You are about to delete this remapping.</value>
|
||||
</data>
|
||||
<data name="DeleteConfirmationDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="DeleteConfirmationDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="TriggerLabel.Text" xml:space="preserve">
|
||||
<value>Trigger</value>
|
||||
</data>
|
||||
<data name="ActionLabel.Text" xml:space="preserve">
|
||||
<value>Action</value>
|
||||
</data>
|
||||
<data name="TriggerType_KeyOrShortcut.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Key or shortcut</value>
|
||||
</data>
|
||||
<data name="TriggerType_KeyOrShortcut_Text.Text" xml:space="preserve">
|
||||
<value>Key or shortcut</value>
|
||||
</data>
|
||||
<data name="TriggerType_Mouse.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Mouse button</value>
|
||||
</data>
|
||||
<data name="TriggerType_Mouse_Text.Text" xml:space="preserve">
|
||||
<value>Mouse button</value>
|
||||
</data>
|
||||
<data name="AllowChordsCheckBox.Content" xml:space="preserve">
|
||||
<value>Allow chords</value>
|
||||
</data>
|
||||
<data name="ExactMatchCheckBox.Content" xml:space="preserve">
|
||||
<value>Exact match</value>
|
||||
</data>
|
||||
<data name="MouseButton_Left.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
</data>
|
||||
<data name="MouseButton_Center.Content" xml:space="preserve">
|
||||
<value>Center</value>
|
||||
</data>
|
||||
<data name="MouseButton_Right.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
</data>
|
||||
<data name="MouseButton_Button1.Content" xml:space="preserve">
|
||||
<value>Button 1</value>
|
||||
</data>
|
||||
<data name="MouseButton_Button2.Content" xml:space="preserve">
|
||||
<value>Button 2</value>
|
||||
</data>
|
||||
<data name="AppSpecificCheckBox.Content" xml:space="preserve">
|
||||
<value>Only apply to a specific app</value>
|
||||
</data>
|
||||
<data name="AppNameTextBox.Header" xml:space="preserve">
|
||||
<value>App name</value>
|
||||
</data>
|
||||
<data name="AppNameTextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Enter app name (e.g., notepad.exe)</value>
|
||||
</data>
|
||||
<data name="ActionType_KeyOrShortcut.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Remap to key or shortcut</value>
|
||||
</data>
|
||||
<data name="ActionType_KeyOrShortcut_Text.Text" xml:space="preserve">
|
||||
<value>Remap to key or shortcut</value>
|
||||
</data>
|
||||
<data name="ActionType_Text.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Insert text</value>
|
||||
</data>
|
||||
<data name="ActionType_Text_Text.Text" xml:space="preserve">
|
||||
<value>Insert text</value>
|
||||
</data>
|
||||
<data name="ActionType_OpenUrl.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Open URL</value>
|
||||
</data>
|
||||
<data name="ActionType_OpenUrl_Text.Text" xml:space="preserve">
|
||||
<value>Open URL</value>
|
||||
</data>
|
||||
<data name="ActionType_OpenApp.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Open app</value>
|
||||
</data>
|
||||
<data name="ActionType_OpenApp_Text.Text" xml:space="preserve">
|
||||
<value>Open app</value>
|
||||
</data>
|
||||
<data name="ActionType_MouseClick.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Remap to mouse click</value>
|
||||
</data>
|
||||
<data name="ActionType_MouseClick_Text.Text" xml:space="preserve">
|
||||
<value>Remap to mouse click</value>
|
||||
</data>
|
||||
<data name="TextContentBox.Header" xml:space="preserve">
|
||||
<value>Text to type</value>
|
||||
</data>
|
||||
<data name="TextContentBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Enter the text to type when triggered</value>
|
||||
</data>
|
||||
<data name="UrlPathInput.Header" xml:space="preserve">
|
||||
<value>URL to open</value>
|
||||
</data>
|
||||
<data name="UrlPathInput.PlaceholderText" xml:space="preserve">
|
||||
<value>https://example.com</value>
|
||||
</data>
|
||||
<data name="ProgramPathInput.Header" xml:space="preserve">
|
||||
<value>Program path</value>
|
||||
</data>
|
||||
<data name="ProgramPathInput.PlaceholderText" xml:space="preserve">
|
||||
<value>C:\Program Files\...</value>
|
||||
</data>
|
||||
<data name="ProgramPathSelectButton.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Select program path</value>
|
||||
</data>
|
||||
<data name="ProgramArgsInput.Header" xml:space="preserve">
|
||||
<value>Arguments (optional)</value>
|
||||
</data>
|
||||
<data name="ProgramArgsInput.PlaceholderText" xml:space="preserve">
|
||||
<value>--arg1 value1</value>
|
||||
</data>
|
||||
<data name="StartInPathInput.Header" xml:space="preserve">
|
||||
<value>Start in directory (optional)</value>
|
||||
</data>
|
||||
<data name="StartInPathInput.PlaceholderText" xml:space="preserve">
|
||||
<value>C:\Users\...</value>
|
||||
</data>
|
||||
<data name="StartInSelectButton.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Select start directory</value>
|
||||
</data>
|
||||
<data name="ElevationComboBox.Header" xml:space="preserve">
|
||||
<value>Run as</value>
|
||||
</data>
|
||||
<data name="Elevation_Normal.Content" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="Elevation_Elevated.Content" xml:space="preserve">
|
||||
<value>Elevated</value>
|
||||
</data>
|
||||
<data name="Elevation_DifferentUser.Content" xml:space="preserve">
|
||||
<value>Different user</value>
|
||||
</data>
|
||||
<data name="IfRunningComboBox.Header" xml:space="preserve">
|
||||
<value>If already running</value>
|
||||
</data>
|
||||
<data name="IfRunning_ShowWindow.Content" xml:space="preserve">
|
||||
<value>Show window</value>
|
||||
</data>
|
||||
<data name="IfRunning_StartAnother.Content" xml:space="preserve">
|
||||
<value>Start another</value>
|
||||
</data>
|
||||
<data name="IfRunning_DoNothing.Content" xml:space="preserve">
|
||||
<value>Do nothing</value>
|
||||
</data>
|
||||
<data name="IfRunning_Close.Content" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
</data>
|
||||
<data name="IfRunning_EndTask.Content" xml:space="preserve">
|
||||
<value>End task</value>
|
||||
</data>
|
||||
<data name="VisibilityComboBox.Header" xml:space="preserve">
|
||||
<value>Window visibility</value>
|
||||
</data>
|
||||
<data name="Visibility_Normal.Content" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="Visibility_Hidden.Content" xml:space="preserve">
|
||||
<value>Hidden</value>
|
||||
</data>
|
||||
<data name="Visibility_Minimized.Content" xml:space="preserve">
|
||||
<value>Minimized</value>
|
||||
</data>
|
||||
<data name="Visibility_Maximized.Content" xml:space="preserve">
|
||||
<value>Maximized</value>
|
||||
</data>
|
||||
<data name="MouseClickPlaceholder.Text" xml:space="preserve">
|
||||
<value>Mouse click action - coming soon</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,759 @@
|
||||
<?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">
|
||||
|
||||
<Style x:Key="CustomShortcutToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="{ThemeResource ToggleButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ToggleButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ToggleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1,1,1,1" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Checked">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1,1,1,4" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1,1,1,4" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1,1,1,4" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Indeterminate">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminatePointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminatePressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminateDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="RightAlignedCompactToggleSwitchStyle"
|
||||
BasedOn="{StaticResource DefaultToggleSwitchStyle}"
|
||||
TargetType="ToggleSwitch">
|
||||
<Style.Setters>
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleSwitch">
|
||||
<Grid
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource ToggleSwitchTopHeaderMargin}"
|
||||
VerticalAlignment="Top"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
Foreground="{ThemeResource ToggleSwitchHeaderForeground}"
|
||||
IsHitTestVisible="False"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="12" MaxWidth="12" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
x:Name="SwitchAreaGrid"
|
||||
Grid.RowSpan="3"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0,5"
|
||||
Background="{ThemeResource ToggleSwitchContainerBackground}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||
<ContentPresenter
|
||||
x:Name="OffContentPresenter"
|
||||
Grid.RowSpan="3"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding OffContent}"
|
||||
ContentTemplate="{TemplateBinding OffContentTemplate}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0" />
|
||||
<ContentPresenter
|
||||
x:Name="OnContentPresenter"
|
||||
Grid.RowSpan="3"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding OnContent}"
|
||||
ContentTemplate="{TemplateBinding OnContentTemplate}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0" />
|
||||
<Rectangle
|
||||
x:Name="OuterBorder"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Width="40"
|
||||
Height="20"
|
||||
Fill="{ThemeResource ToggleSwitchFillOff}"
|
||||
RadiusX="10"
|
||||
RadiusY="10"
|
||||
Stroke="{ThemeResource ToggleSwitchStrokeOff}"
|
||||
StrokeThickness="{ThemeResource ToggleSwitchOuterBorderStrokeThickness}" />
|
||||
<Rectangle
|
||||
x:Name="SwitchKnobBounds"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Width="40"
|
||||
Height="20"
|
||||
Fill="{ThemeResource ToggleSwitchFillOn}"
|
||||
Opacity="0"
|
||||
RadiusX="10"
|
||||
RadiusY="10"
|
||||
Stroke="{ThemeResource ToggleSwitchStrokeOn}"
|
||||
StrokeThickness="{ThemeResource ToggleSwitchOnStrokeThickness}" />
|
||||
<Grid
|
||||
x:Name="SwitchKnob"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left">
|
||||
<Border
|
||||
x:Name="SwitchKnobOn"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Margin="0,0,3,0"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{ThemeResource ToggleSwitchKnobFillOn}"
|
||||
BackgroundSizing="OuterBorderEdge"
|
||||
BorderBrush="{ThemeResource ToggleSwitchKnobStrokeOn}"
|
||||
CornerRadius="7"
|
||||
Opacity="0"
|
||||
RenderTransformOrigin="0.5, 0.5">
|
||||
<Border.RenderTransform>
|
||||
<CompositeTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
<Rectangle
|
||||
x:Name="SwitchKnobOff"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Margin="3,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource ToggleSwitchKnobFillOff}"
|
||||
RadiusX="7"
|
||||
RadiusY="7"
|
||||
RenderTransformOrigin="0.5, 0.5">
|
||||
<Rectangle.RenderTransform>
|
||||
<CompositeTransform />
|
||||
</Rectangle.RenderTransform>
|
||||
</Rectangle>
|
||||
<Grid.RenderTransform>
|
||||
<TranslateTransform x:Name="KnobTranslateTransform" />
|
||||
</Grid.RenderTransform>
|
||||
</Grid>
|
||||
<Thumb
|
||||
x:Name="SwitchThumb"
|
||||
Grid.RowSpan="3"
|
||||
Grid.ColumnSpan="3"
|
||||
AutomationProperties.AccessibilityView="Raw">
|
||||
<Thumb.Template>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Rectangle Fill="Transparent" />
|
||||
</ControlTemplate>
|
||||
</Thumb.Template>
|
||||
</Thumb>
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Stroke">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOff}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOff}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOff}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOn}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOn}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOn}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContainerBackground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPointerOver}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPointerOver}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPointerOver}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="14" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="14" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="14" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="14" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SwitchKnobOn.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="SwitchKnobOn.Margin" Value="0,0,3,0" />
|
||||
<Setter Target="SwitchKnobOff.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="SwitchKnobOff.Margin" Value="3,0,0,0" />
|
||||
</VisualState.Setters>
|
||||
<Storyboard>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPressed}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPressed}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPressed}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="17" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="14" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="17" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||
Value="14" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchHeaderForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffDisabled}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffDisabled}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
|
||||
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundDisabled}" />
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOn"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Width">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames
|
||||
EnableDependentAnimation="True"
|
||||
Storyboard.TargetName="SwitchKnobOff"
|
||||
Storyboard.TargetProperty="Height">
|
||||
<SplineDoubleKeyFrame
|
||||
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||
Value="12" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="ToggleStates">
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition
|
||||
x:Name="DraggingToOnTransition"
|
||||
GeneratedDuration="0"
|
||||
From="Dragging"
|
||||
To="On">
|
||||
<Storyboard>
|
||||
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOnOffset}" TargetName="SwitchKnob" />
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
<VisualTransition
|
||||
x:Name="OnToDraggingTransition"
|
||||
GeneratedDuration="0"
|
||||
From="On"
|
||||
To="Dragging">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="0" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="0" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="0" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
<VisualTransition
|
||||
x:Name="DraggingToOffTransition"
|
||||
GeneratedDuration="0"
|
||||
From="Dragging"
|
||||
To="Off">
|
||||
<Storyboard>
|
||||
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOffOffset}" TargetName="SwitchKnob" />
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
<VisualTransition
|
||||
x:Name="OnToOffTransition"
|
||||
GeneratedDuration="0"
|
||||
From="On"
|
||||
To="Off">
|
||||
<Storyboard>
|
||||
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOnToOffOffset}" TargetName="SwitchKnob" />
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
<VisualTransition
|
||||
x:Name="OffToOnTransition"
|
||||
GeneratedDuration="0"
|
||||
From="Off"
|
||||
To="On">
|
||||
<Storyboard>
|
||||
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOffToOnOffset}" TargetName="SwitchKnob" />
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
</VisualStateGroup.Transitions>
|
||||
<VisualState x:Name="Dragging" />
|
||||
<VisualState x:Name="Off" />
|
||||
<VisualState x:Name="On">
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="KnobTranslateTransform"
|
||||
Storyboard.TargetProperty="X"
|
||||
To="20"
|
||||
Duration="0" />
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="ContentStates">
|
||||
<VisualState x:Name="OffContent">
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="OffContentPresenter"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0" />
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
|
||||
<DiscreteObjectKeyFrame KeyTime="0">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<x:Boolean>True</x:Boolean>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="OnContent">
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="OnContentPresenter"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0" />
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
|
||||
<DiscreteObjectKeyFrame KeyTime="0">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<x:Boolean>True</x:Boolean>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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">
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<SolidColorBrush
|
||||
x:Key="CustomAccentBackgroundBrush"
|
||||
Opacity="0.3"
|
||||
Color="{ThemeResource SystemAccentColorLight2}" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush
|
||||
x:Key="CustomAccentBackgroundBrush"
|
||||
Opacity="0.2"
|
||||
Color="{ThemeResource SystemAccentColorDark1}" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="CustomAccentBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user