diff --git a/TaskbarLauncher.csproj b/TaskbarLauncher.csproj index da33b73..4db4170 100644 --- a/TaskbarLauncher.csproj +++ b/TaskbarLauncher.csproj @@ -12,8 +12,8 @@ Ray Berezowski Assets\AppIcon.ico 1.0.1 - 1.0.1.15 - 1.0.1.15 + 1.0.1.16 + 1.0.1.16 1.0.1 diff --git a/Views/LauncherPopup.xaml.cs b/Views/LauncherPopup.xaml.cs index 4611b4e..fd0120c 100644 --- a/Views/LauncherPopup.xaml.cs +++ b/Views/LauncherPopup.xaml.cs @@ -14,6 +14,7 @@ public partial class LauncherPopup : Window private const double HeaderAndChromeHeight = 118; private bool isRepositionQueued; private readonly List visibleLaunchButtons = []; + private readonly List visibleNavigationControls = []; private readonly LauncherConfig config; private readonly Action launchItem; @@ -96,6 +97,7 @@ public partial class LauncherPopup : Window { ItemsPanel.Children.Clear(); visibleLaunchButtons.Clear(); + visibleNavigationControls.Clear(); if (config.RecentItems.Any(item => !item.IsDisabled)) { @@ -107,6 +109,8 @@ public partial class LauncherPopup : Window { ItemsPanel.Children.Add(CreateItemControl(item, 0, expandMenusByDefault)); } + + RebuildNavigationLists(); } private Expander CreateRecentItemsControl() @@ -115,8 +119,12 @@ public partial class LauncherPopup : Window { Header = new TextBlock { Text = "Recent" }, IsExpanded = true, - Margin = new Thickness(0, 4, 0, 8) + Margin = new Thickness(0, 4, 0, 8), + Focusable = true }; + expander.KeyDown += NavigationControl_KeyDown; + expander.Expanded += (_, _) => RebuildNavigationAndReposition(); + expander.Collapsed += (_, _) => RebuildNavigationAndReposition(); StackPanel recentItems = new(); foreach (LauncherItem item in config.RecentItems.Where(item => !item.IsDisabled)) @@ -136,8 +144,13 @@ public partial class LauncherPopup : Window { Header = CreateMenuHeader(item), IsExpanded = expandMenusByDefault, - Margin = new Thickness(depth * 12, 4, 0, 4) + Margin = new Thickness(depth * 12, 4, 0, 4), + Focusable = true, + Tag = item }; + expander.KeyDown += NavigationControl_KeyDown; + expander.Expanded += (_, _) => RebuildNavigationAndReposition(); + expander.Collapsed += (_, _) => RebuildNavigationAndReposition(); StackPanel children = new(); foreach (LauncherItem child in GetDisplayItems(item.Children)) @@ -165,7 +178,6 @@ public partial class LauncherPopup : Window } button.KeyDown += LaunchButton_KeyDown; - visibleLaunchButtons.Add(button); return button; } @@ -329,6 +341,8 @@ public partial class LauncherPopup : Window { ItemsPanel.Children.Add(CreateSearchResultControl(match)); } + + RebuildNavigationLists(); } private FrameworkElement CreateSearchResultControl(SearchMatch match) @@ -346,10 +360,49 @@ public partial class LauncherPopup : Window button.Click += (_, _) => launchItem(match.Item); button.KeyDown += LaunchButton_KeyDown; - visibleLaunchButtons.Add(button); return button; } + private void RebuildNavigationAndReposition() + { + RebuildNavigationLists(); + QueueReposition(); + } + + private void RebuildNavigationLists() + { + visibleLaunchButtons.Clear(); + visibleNavigationControls.Clear(); + AddVisibleNavigationControls(ItemsPanel.Children); + } + + private void AddVisibleNavigationControls(UIElementCollection elements) + { + foreach (UIElement element in elements) + { + switch (element) + { + case Expander expander: + visibleNavigationControls.Add(expander); + if (expander.IsExpanded && expander.Content is System.Windows.Controls.Panel panel) + { + AddVisibleNavigationControls(panel.Children); + } + + break; + + case System.Windows.Controls.Button button: + visibleNavigationControls.Add(button); + if (button.Tag is LauncherItem) + { + visibleLaunchButtons.Add(button); + } + + break; + } + } + } + private object CreateSearchResultContent(SearchMatch match) { StackPanel panel = new(); @@ -415,14 +468,14 @@ public partial class LauncherPopup : Window if (e.Key == Key.Down) { - FocusLaunchButton(0); + FocusNavigationControl(0); e.Handled = true; return; } if (e.Key == Key.Up) { - FocusLaunchButton(visibleLaunchButtons.Count - 1); + FocusNavigationControl(visibleNavigationControls.Count - 1); e.Handled = true; return; } @@ -462,7 +515,37 @@ public partial class LauncherPopup : Window return; } - int index = visibleLaunchButtons.IndexOf(button); + HandleNavigationControlKey(button, e); + } + + private void NavigationControl_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (sender is not System.Windows.Controls.Control control) + { + return; + } + + if (e.Key == Key.Enter && control is Expander expander) + { + expander.IsExpanded = !expander.IsExpanded; + QueueReposition(); + e.Handled = true; + return; + } + + if (e.Key == Key.Escape) + { + Close(); + e.Handled = true; + return; + } + + HandleNavigationControlKey(control, e); + } + + private void HandleNavigationControlKey(System.Windows.Controls.Control control, System.Windows.Input.KeyEventArgs e) + { + int index = visibleNavigationControls.IndexOf(control); if (index < 0) { return; @@ -470,7 +553,7 @@ public partial class LauncherPopup : Window if (e.Key == Key.Down) { - FocusLaunchButton(index + 1); + FocusNavigationControl(index + 1); e.Handled = true; return; } @@ -484,7 +567,7 @@ public partial class LauncherPopup : Window } else { - FocusLaunchButton(index - 1); + FocusNavigationControl(index - 1); } e.Handled = true; @@ -493,30 +576,30 @@ public partial class LauncherPopup : Window if (e.Key == Key.Home) { - FocusLaunchButton(0); + FocusNavigationControl(0); e.Handled = true; return; } if (e.Key == Key.End) { - FocusLaunchButton(visibleLaunchButtons.Count - 1); + FocusNavigationControl(visibleNavigationControls.Count - 1); e.Handled = true; } } - private void FocusLaunchButton(int index) + private void FocusNavigationControl(int index) { - if (visibleLaunchButtons.Count == 0) + if (visibleNavigationControls.Count == 0) { return; } - int clampedIndex = Math.Clamp(index, 0, visibleLaunchButtons.Count - 1); - System.Windows.Controls.Button button = visibleLaunchButtons[clampedIndex]; + int clampedIndex = Math.Clamp(index, 0, visibleNavigationControls.Count - 1); + System.Windows.Controls.Control control = visibleNavigationControls[clampedIndex]; Keyboard.ClearFocus(); - button.Focus(); - button.BringIntoView(); + control.Focus(); + control.BringIntoView(); } protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)