Browse Source

Shortcuts minor rework wip

Krzysztof Krysiński 2 years ago
parent
commit
a30568da74

+ 1 - 0
src/PixiEditor/Helpers/InputKeyHelpers.cs

@@ -17,6 +17,7 @@ internal static class InputKeyHelpers
         >= Key.NumPad0 and <= Key.Divide => $"Num {GetMappedKey(key, culture)}",
         Key.Space => nameof(Key.Space),
         Key.Tab => nameof(Key.Tab),
+        Key.Return => "Enter",
         Key.Back => "Backspace",
         Key.Escape => "Esc",
         _ => GetMappedKey(key, culture),

BIN
src/PixiEditor/Images/TemplateLogos/Aseprite.png


+ 5 - 6
src/PixiEditor/Models/Commands/CommandController.cs

@@ -45,13 +45,13 @@ internal class CommandController
         IconEvaluators = new();
     }
 
-    public void Import(IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> shortcuts, bool save = true)
+    public void Import(List<Shortcut> shortcuts, bool save = true)
     {
         foreach (var shortcut in shortcuts)
         {
-            foreach (var command in shortcut.Value)
+            foreach (var command in shortcut.Commands)
             {
-                ReplaceShortcut(Commands[command], shortcut.Key);
+                ReplaceShortcut(Commands[command], shortcut.KeyCombination);
             }
         }
 
@@ -92,8 +92,7 @@ internal class CommandController
 
     public void Init(IServiceProvider serviceProvider)
     {
-        KeyValuePair<KeyCombination, IEnumerable<string>>[] shortcuts = shortcutFile.LoadShortcuts()?.ToArray()
-                                                                        ?? Array.Empty<KeyValuePair<KeyCombination, IEnumerable<string>>>();
+        ShortcutsTemplate template = shortcutFile.LoadTemplate();
 
         Type[] allTypesInPixiEditorAssembly = typeof(CommandController).Assembly.GetTypes();
 
@@ -204,7 +203,7 @@ internal class CommandController
         }
 
         KeyCombination GetShortcut(string internalName, KeyCombination defaultShortcut)
-            => shortcuts.FirstOrDefault(x => x.Value.Contains(internalName), new(defaultShortcut, null)).Key;
+            => template.Shortcuts.FirstOrDefault(x => x.Commands.Contains(internalName), new Shortcut(defaultShortcut, (List<string>)null)).KeyCombination;
 
         void AddCommandToCommandsCollection(Command command)
         {

+ 17 - 7
src/PixiEditor/Models/Commands/ShortcutFile.cs

@@ -7,7 +7,6 @@ namespace PixiEditor.Models.Commands;
 internal class ShortcutFile
 {
     private readonly CommandController _commands;
-
     public string Path { get; }
 
     public ShortcutFile(string path, CommandController controller)
@@ -24,21 +23,32 @@ internal class ShortcutFile
 
     public void SaveShortcuts()
     {
-        OneToManyDictionary<KeyCombination, string> shortcuts = new();
+        List<Shortcut> shortcuts = new();
 
         foreach (var shortcut in _commands.Commands.GetShortcuts())
         {
             foreach (var command in shortcut.Value.Where(x => x.Shortcut != x.DefaultShortcut))
             {
-                shortcuts.Add(shortcut.Key, command.InternalName);
+                Shortcut shortcutToAdd = new Shortcut(shortcut.Key, new List<string> { command.InternalName });
+                shortcuts.Add(shortcutToAdd);
             }
         }
+        
+        ShortcutsTemplate template = new()
+        {
+            Shortcuts = shortcuts.ToList(),
+        };
 
-        File.WriteAllText(Path, JsonConvert.SerializeObject(shortcuts));
+        File.WriteAllText(Path, JsonConvert.SerializeObject(template));
     }
 
-    public IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> LoadShortcuts() => LoadShortcuts(Path);
+    public ShortcutsTemplate LoadTemplate() => LoadTemplate(Path);
 
-    public static IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> LoadShortcuts(string path) =>
-        JsonConvert.DeserializeObject<IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>>>(File.ReadAllText(path));
+    public static ShortcutsTemplate LoadTemplate(string path)
+    {
+        var template = JsonConvert.DeserializeObject<ShortcutsTemplate>(File.ReadAllText(path));
+        if (template == null) return new ShortcutsTemplate();
+
+        return template;
+    }
 }

+ 44 - 0
src/PixiEditor/Models/Commands/ShortcutsTemplate.cs

@@ -0,0 +1,44 @@
+using System.Drawing;
+using System.IO;
+using System.Windows.Input;
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Models.Commands;
+
+[Serializable]
+public sealed class ShortcutsTemplate
+{
+    public List<Shortcut> Shortcuts { get; set; }
+
+    public ShortcutsTemplate()
+    {
+        Shortcuts = new List<Shortcut>();
+    }
+}
+
+[Serializable]
+public sealed class Shortcut
+{
+    public KeyCombination KeyCombination { get; set; }
+    public List<string> Commands { get; set; }
+    
+    public Shortcut() {}
+
+    public Shortcut(KeyCombination keyCombination, List<string> commands)
+    {
+        KeyCombination = keyCombination;
+        Commands = commands;
+    }
+    
+    public Shortcut(KeyCombination keyCombination, string command)
+    {
+        KeyCombination = keyCombination;
+        Commands = new List<string> { command };
+    }
+    
+    public Shortcut(Key key, ModifierKeys modifierKeys, string command)
+    {
+        KeyCombination = new KeyCombination(key, modifierKeys);
+        Commands = new List<string> { command };
+    }
+}

+ 1 - 1
src/PixiEditor/Models/Commands/Templates/IShortcutDefaults.cs

@@ -2,5 +2,5 @@
 
 internal interface IShortcutDefaults
 {
-    ShortcutCollection DefaultShortcuts { get; }
+    List<Shortcut> DefaultShortcuts { get; }
 }

+ 1 - 1
src/PixiEditor/Models/Commands/Templates/IShortcutFile.cs

@@ -4,5 +4,5 @@ internal interface IShortcutFile
 {
     string Filter { get; }
 
-    ShortcutCollection GetShortcuts(string path);
+    ShortcutsTemplate GetShortcutsTemplate(string path);
 }

+ 1 - 1
src/PixiEditor/Models/Commands/Templates/IShortcutInstallation.cs

@@ -4,5 +4,5 @@ internal interface IShortcutInstallation
 {
     bool InstallationPresent { get; }
 
-    ShortcutCollection GetInstalledShortcuts();
+    ShortcutsTemplate GetInstalledShortcuts();
 }

+ 9 - 8
src/PixiEditor/Models/Commands/Templates/Providers/AsepriteProvider.cs

@@ -10,19 +10,20 @@ internal partial class ShortcutProvider
     {
         public AsepriteProvider() : base("Aseprite")
         {
+            LogoPath = "/Images/TemplateLogos/Aseprite.png";
         }
 
-        public ShortcutCollection DefaultShortcuts { get; } = new()
+        public List<Shortcut> DefaultShortcuts { get; } = new()
         {
-            { "PixiEditor.File.SaveAsNew", Key.S, ModifierKeys.Control | ModifierKeys.Alt },
-            { "PixiEditor.Window.OpenSettingsWindow", Key.K, ModifierKeys.Control },
+            new Shortcut(Key.S, ModifierKeys.Control | ModifierKeys.Alt, "PixiEditor.File.SaveAsNew"),
+            new Shortcut(Key.K, ModifierKeys.Control, "PixiEditor.Window.OpenSettingsWindow"),
             // Tools
-            { "PixiEditor.Tools.Select.EllipseToolViewModel", Key.U, ModifierKeys.Shift },
-            { "PixiEditor.Tools.Select.ColorPickerToolViewModel", Key.I, ModifierKeys.None },
-            { "PixiEditor.Tools.Select.RectangleToolViewModel", Key.U, ModifierKeys.None },
-            { "PixiEditor.Tools.Select.SelectToolViewModel", Key.V, ModifierKeys.None },
+            new Shortcut(Key.U, ModifierKeys.Shift, "PixiEditor.Tools.Select.EllipseToolViewModel"),
+            new Shortcut(Key.I, ModifierKeys.None, "PixiEditor.Tools.Select.ColorPickerToolViewModel"),
+            new Shortcut(Key.U, ModifierKeys.None, "PixiEditor.Tools.Select.RectangleToolViewModel"),
+            new Shortcut(Key.V, ModifierKeys.None, "PixiEditor.Tools.Select.SelectToolViewModel"),
             // Not actually in aseprite, but should be included
-            { "PixiEditor.Search.Toggle", Key.OemComma, ModifierKeys.Control }
+            new Shortcut(Key.OemComma, ModifierKeys.Control, "PixiEditor.Search.Toggle")
         };
     }
 }

+ 6 - 6
src/PixiEditor/Models/Commands/Templates/Providers/DebugProvider.cs

@@ -19,20 +19,20 @@ internal partial class ShortcutProvider
         {
         }
 
-        public ShortcutCollection DefaultShortcuts { get; } = new()
+        public List<Shortcut> DefaultShortcuts { get; } = new()
         {
             // Add shortcuts for undo and redo
-            { "PixiEditor.Undo.Undo", Key.Z, ModifierKeys.Control },
-            { "PixiEditor.Undo.Redo", Key.Y, ModifierKeys.Control },
-            "PixiEditor.Colors.Swap"
+            new Shortcut(Key.Z, ModifierKeys.Control, "PixiEditor.Undo.Undo"),
+            new Shortcut(Key.Y, ModifierKeys.Control, "PixiEditor.Undo.Redo"),
+            new Shortcut(Key.X, ModifierKeys.None, "PixiEditor.Colors.Swap")
         };
 
         public string Filter => "json (*.json)|*.json";
 
-        public ShortcutCollection GetShortcuts(string path) => new(ShortcutFile.LoadShortcuts(path));
+        public ShortcutsTemplate GetShortcutsTemplate(string path) => ShortcutFile.LoadTemplate(path);
 
         public bool InstallationPresent => File.Exists(InstallationPath);
 
-        public ShortcutCollection GetInstalledShortcuts() => new(ShortcutFile.LoadShortcuts(InstallationPath));
+        public ShortcutsTemplate GetInstalledShortcuts() => ShortcutFile.LoadTemplate(InstallationPath);
     }
 }

+ 4 - 4
src/PixiEditor/Models/Commands/Templates/ShortcutCollection.cs

@@ -3,16 +3,16 @@ using PixiEditor.Models.DataHolders;
 
 namespace PixiEditor.Models.Commands.Templates;
 
-internal class ShortcutCollection : OneToManyDictionary<KeyCombination, string>
+internal class ShortcutCollection : List<Shortcut>
 {
     public ShortcutCollection() { }
 
-    public ShortcutCollection(IEnumerable<KeyValuePair<KeyCombination, IEnumerable<string>>> enumerable) : base(enumerable)
+    public ShortcutCollection(List<Shortcut> enumerable) : base(enumerable)
     { }
 
     public void Add(string commandName, Key key, ModifierKeys modifiers)
     {
-        Add(new(key, modifiers), commandName);
+        Add(new Shortcut(new KeyCombination(key, modifiers), new List<string>() { commandName }));
     }
 
     /// <summary>
@@ -20,6 +20,6 @@ internal class ShortcutCollection : OneToManyDictionary<KeyCombination, string>
     /// </summary>
     public void Add(string commandName)
     {
-        Add(KeyCombination.None, commandName);
+        Add(new Shortcut(KeyCombination.None, new List<string> { commandName }));
     }
 }

+ 2 - 0
src/PixiEditor/Models/Commands/Templates/ShortcutProvider.cs

@@ -5,6 +5,8 @@ namespace PixiEditor.Models.Commands.Templates;
 internal partial class ShortcutProvider
 {
     public string Name { get; set; }
+    
+    public string LogoPath { get; set; }
 
     /// <summary>
     /// Set this to true if this provider has default shortcuts

+ 2 - 0
src/PixiEditor/PixiEditor.csproj

@@ -333,6 +333,8 @@
 		<Resource Include="Images\Settings.png" />
 		<None Remove="Images\Tools\LassoImage.png" />
 		<Resource Include="Images\Tools\LassoImage.png" />
+		<None Remove="Images\TemplateLogos\Aseprite.png" />
+		<Resource Include="Images\TemplateLogos\Aseprite.png" />
 	</ItemGroup>
 	<ItemGroup>
 		<None Include="..\LICENSE">

+ 1 - 1
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -92,7 +92,7 @@ internal class SettingsWindowViewModel : ViewModelBase
         dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
         if (dialog.ShowDialog().GetValueOrDefault())
         {
-            var shortcuts = ShortcutFile.LoadShortcuts(dialog.FileName)?.ToArray();
+            var shortcuts = ShortcutFile.LoadTemplate(dialog.FileName)?.Shortcuts.ToList();
             if (shortcuts is null)
             {
                 NoticeDialog.Show("Shortcuts file was not in a valid format", "Invalid file");

+ 1 - 0
src/PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml

@@ -41,6 +41,7 @@
                         </Grid.ColumnDefinitions>
                         <TextBlock Text="{Binding Name}" VerticalAlignment="Center"
                                    ToolTip="{Binding Description}"/>
+                        <Image Source="{Binding LogoPath}" Width="48" Height="48"/>
                         <StackPanel Grid.Column="1" Orientation="Horizontal">
                             <StackPanel.Resources>
                                 <Style TargetType="Button" BasedOn="{StaticResource DarkRoundButton}">

+ 5 - 5
src/PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml.cs

@@ -28,7 +28,7 @@ internal partial class ImportShortcutTemplatePopup : Window
     {
         if (provider is not IShortcutDefaults defaults)
         {
-            throw new ArgumentException("provider must implement IShortcutDefaults", nameof(provider));
+            throw new ArgumentException("Provider must implement IShortcutDefaults", nameof(provider));
         }
 
         CommandController.Current.ResetShortcuts();
@@ -54,10 +54,10 @@ internal partial class ImportShortcutTemplatePopup : Window
 
         try
         {
-            var shortcuts = defaults.GetShortcuts(picker.FileName);
+            var template = defaults.GetShortcutsTemplate(picker.FileName);
 
             CommandController.Current.ResetShortcuts();
-            CommandController.Current.Import(shortcuts);
+            CommandController.Current.Import(template.Shortcuts);
         }
         catch (FileFormatException)
         {
@@ -73,14 +73,14 @@ internal partial class ImportShortcutTemplatePopup : Window
     {
         if (provider is not IShortcutInstallation defaults)
         {
-            throw new ArgumentException("provider must implement IShortcutInstallation", nameof(provider));
+            throw new ArgumentException("Provider must implement IShortcutInstallation", nameof(provider));
         }
 
         CommandController.Current.ResetShortcuts();
 
         try
         {
-            CommandController.Current.Import(defaults.GetInstalledShortcuts());
+            CommandController.Current.Import(defaults.GetInstalledShortcuts().Shortcuts);
         }
         catch
         {

+ 55 - 0
src/PixiEditor/Views/Dialogs/SettingGroups/ShortcutsBinder.xaml

@@ -0,0 +1,55 @@
+<UserControl x:Class="PixiEditor.Views.Dialogs.SettingGroups.ShortcutsBinder"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:PixiEditor.Views.Dialogs.SettingGroups"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
+             mc:Ignorable="d"
+             d:DesignHeight="600" d:DesignWidth="400">
+    <ScrollViewer x:Name="commandScroll">
+                    <ScrollViewer.Template>
+                        <ControlTemplate TargetType="{x:Type ScrollViewer}">
+                            <Grid x:Name="Grid" Background="{TemplateBinding Background}">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="*"/>
+                                    <ColumnDefinition Width="Auto"/>
+                                </Grid.ColumnDefinitions>
+                                <Grid.RowDefinitions>
+                                    <RowDefinition Height="*"/>
+                                    <RowDefinition Height="Auto"/>
+                                </Grid.RowDefinitions>
+                                <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
+                                <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
+                                <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" Margin="0,5,0,20"/>
+                                <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
+                            </Grid>
+                        </ControlTemplate>
+                    </ScrollViewer.Template>
+                    <Grid>
+                        <TextBlock Foreground="LightGray" HorizontalAlignment="Center" TextAlignment="Center"
+                                   Visibility="{Binding VisibleGroups, ConverterParameter=0, Mode=OneWay, Converter={converters:EqualityBoolToVisibilityConverter}}"
+                                   Text="Nothing found"/>
+                        <ItemsControl ItemsSource="{Binding Commands}" Foreground="White">
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <StackPanel Margin="0,0,0,20" Visibility="{Binding Visibility}">
+                                        <TextBlock Text="{Binding DisplayName}" FontSize="22" FontWeight="SemiBold"/>
+                                        <ItemsControl ItemsSource="{Binding Commands}">
+                                            <ItemsControl.ItemTemplate>
+                                                <DataTemplate>
+                                                    <Grid Margin="0,5,5,0" Visibility="{Binding Visibility}">
+                                                        <TextBlock Text="{Binding Command.DisplayName}" ToolTip="{Binding Command.Description}"/>
+                                                        <userControls:ShortcutBox Width="120" Command="{Binding Command}" HorizontalAlignment="Right"/>
+                                                    </Grid>
+                                                </DataTemplate>
+                                            </ItemsControl.ItemTemplate>
+                                        </ItemsControl>
+                                    </StackPanel>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
+                    </Grid>
+                </ScrollViewer>
+</UserControl>

+ 12 - 0
src/PixiEditor/Views/Dialogs/SettingGroups/ShortcutsBinder.xaml.cs

@@ -0,0 +1,12 @@
+using System.Windows.Controls;
+
+namespace PixiEditor.Views.Dialogs.SettingGroups;
+
+public partial class ShortcutsBinder : UserControl
+{
+    public ShortcutsBinder()
+    {
+        InitializeComponent();
+    }
+}
+

+ 2 - 44
src/PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -15,6 +15,7 @@
         xmlns:dial="clr-namespace:PixiEditor.Views.Dialogs"
         xmlns:commands="clr-namespace:PixiEditor.Models.Commands"
         xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML"
+        xmlns:settingGroups="clr-namespace:PixiEditor.Views.Dialogs.SettingGroups"
         mc:Ignorable="d"
         Title="Settings" Name="window" 
         Height="688" Width="780"
@@ -211,50 +212,7 @@
                 <TextBox Grid.Row="1" Style="{StaticResource DarkTextBoxStyle}" Margin="0,10"
                          Text="{Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
 
-                <ScrollViewer Grid.Row="2" x:Name="commandScroll">
-                    <ScrollViewer.Template>
-                        <ControlTemplate TargetType="{x:Type ScrollViewer}">
-                            <Grid x:Name="Grid" Background="{TemplateBinding Background}">
-                                <Grid.ColumnDefinitions>
-                                    <ColumnDefinition Width="*"/>
-                                    <ColumnDefinition Width="Auto"/>
-                                </Grid.ColumnDefinitions>
-                                <Grid.RowDefinitions>
-                                    <RowDefinition Height="*"/>
-                                    <RowDefinition Height="Auto"/>
-                                </Grid.RowDefinitions>
-                                <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
-                                <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
-                                <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" Margin="0,5,0,20"/>
-                                <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
-                            </Grid>
-                        </ControlTemplate>
-                    </ScrollViewer.Template>
-                    <Grid>
-                        <TextBlock Foreground="LightGray" HorizontalAlignment="Center" TextAlignment="Center"
-                                   Visibility="{Binding VisibleGroups, ConverterParameter=0, Mode=OneWay, Converter={converters:EqualityBoolToVisibilityConverter}}"
-                                   Text="Nothing found"/>
-                        <ItemsControl ItemsSource="{Binding Commands}" Foreground="White">
-                            <ItemsControl.ItemTemplate>
-                                <DataTemplate>
-                                    <StackPanel Margin="0,0,0,20" Visibility="{Binding Visibility}">
-                                        <TextBlock Text="{Binding DisplayName}" FontSize="22" FontWeight="SemiBold"/>
-                                        <ItemsControl ItemsSource="{Binding Commands}">
-                                            <ItemsControl.ItemTemplate>
-                                                <DataTemplate>
-                                                    <Grid Margin="0,5,5,0" Visibility="{Binding Visibility}">
-                                                        <TextBlock Text="{Binding Command.DisplayName}" ToolTip="{Binding Command.Description}"/>
-                                                        <usercontrols:ShortcutBox Width="120" Command="{Binding Command}" HorizontalAlignment="Right"/>
-                                                    </Grid>
-                                                </DataTemplate>
-                                            </ItemsControl.ItemTemplate>
-                                        </ItemsControl>
-                                    </StackPanel>
-                                </DataTemplate>
-                            </ItemsControl.ItemTemplate>
-                        </ItemsControl>
-                    </Grid>
-                </ScrollViewer>
+                <settingGroups:ShortcutsBinder Grid.Row="2"/>
 
                 <Grid Grid.Row="2" Height="10" VerticalAlignment="Top"
                       Visibility="{Binding VerticalOffset, ElementName=commandScroll, Mode=OneWay, Converter={converters:EqualityBoolToVisibilityConverter Invert=True}, ConverterParameter=0}"

+ 16 - 0
src/PixiEditor/Views/UserControls/ShortcutsTemplateCard.xaml

@@ -0,0 +1,16 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.ShortcutsTemplateCard"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls"
+             mc:Ignorable="d" Background="Transparent"
+             d:DesignHeight="300" d:DesignWidth="300" Name="card">
+    <Border BorderThickness="1" Background="{StaticResource AccentColor}" CornerRadius="15">
+        <Grid>
+            <Image Source="{Binding ElementName=card, Path=ShortcutTemplate.Logo}"/>
+            <Label Style="{StaticResource BaseLabel}" Target="card" 
+                   Content="{Binding ElementName=card, Path=ShortcutTemplate.Name}"/>
+        </Grid>
+    </Border>
+</UserControl>

+ 13 - 0
src/PixiEditor/Views/UserControls/ShortcutsTemplateCard.xaml.cs

@@ -0,0 +1,13 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PixiEditor.Views.UserControls;
+
+public partial class ShortcutsTemplateCard : UserControl
+{
+    public ShortcutsTemplateCard()
+    {
+        InitializeComponent();
+    }
+}
+