From 9f1b012d1513e1727ad0c8138997e092c24eccb8 Mon Sep 17 00:00:00 2001 From: Ray Berezowski <6616212+rberezowski@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:41:20 -0400 Subject: [PATCH] Add pinned launcher items --- MainWindow.xaml.cs | 28 +++++++++++++++++++++++++++- Models/LauncherConfig.cs | 2 ++ README.md | 1 + Services/ConfigService.cs | 1 + Services/LauncherController.cs | 1 + TaskbarLauncher.csproj | 4 ++-- Views/LauncherPopup.xaml.cs | 11 ++++++++--- config.example.json | 5 +++++ docs/INSTALL.md | 1 + 9 files changed, 48 insertions(+), 6 deletions(-) diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index c4b2e48..23ed20b 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -84,15 +84,22 @@ public partial class MainWindow : Window private TreeViewItem CreateTreeItem(LauncherItem item) { + var pinMenuItem = new System.Windows.Controls.MenuItem + { + Header = item.IsFavorite ? "Unpin from Top" : "Pin to Top" + }; + pinMenuItem.Click += ToggleFavorite_Click; + var duplicateMenuItem = new System.Windows.Controls.MenuItem { Header = "Duplicate Item" }; duplicateMenuItem.Click += DuplicateItem_Click; var treeItem = new TreeViewItem { - Header = $"{item.Title} ({item.Type})", + Header = $"{(item.IsFavorite ? "[Pinned] " : "")}{item.Title} ({item.Type})", Tag = item, ContextMenu = new System.Windows.Controls.ContextMenu() }; + treeItem.ContextMenu.Items.Add(pinMenuItem); treeItem.ContextMenu.Items.Add(duplicateMenuItem); treeItem.PreviewMouseRightButtonDown += (_, e) => { @@ -803,6 +810,23 @@ public partial class MainWindow : Window StatusText.Text = "Added submenu. Rename it, then Save."; } + private void ToggleFavorite_Click(object sender, RoutedEventArgs e) + { + if (controller is null || selectedItem is null) + { + return; + } + + ApplyCurrentItem(); + selectedItem.IsFavorite = !selectedItem.IsFavorite; + LauncherItem itemToSelect = selectedItem; + selectedPath = FindItemPath(itemToSelect); + RefreshView(itemToSelect); + StatusText.Text = itemToSelect.IsFavorite + ? $"Pinned {itemToSelect.Title}. Click Save to keep it." + : $"Unpinned {itemToSelect.Title}. Click Save to keep it."; + } + private void DuplicateItem_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) @@ -865,6 +889,7 @@ public partial class MainWindow : Window Target = item.Target, Arguments = item.Arguments, IconPath = item.IconPath, + IsFavorite = item.IsFavorite, DisplayMode = item.DisplayMode, Children = item.Children.Select(child => DuplicateItem(child, renameRoot: false)).ToList() }; @@ -1456,6 +1481,7 @@ public partial class MainWindow : Window Target = TargetBox.Text.Trim(), Arguments = string.IsNullOrWhiteSpace(ArgumentsBox.Text) ? null : ArgumentsBox.Text.Trim(), IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim(), + IsFavorite = selectedItem.IsFavorite, DisplayMode = ItemDisplayModeBox.SelectedItem is LauncherDisplayMode mode ? mode : selectedItem.DisplayMode diff --git a/Models/LauncherConfig.cs b/Models/LauncherConfig.cs index abb5490..0ade9b6 100644 --- a/Models/LauncherConfig.cs +++ b/Models/LauncherConfig.cs @@ -25,6 +25,8 @@ public sealed class LauncherItem public string? IconPath { get; set; } + public bool IsFavorite { get; set; } + public LauncherDisplayMode DisplayMode { get; set; } = LauncherDisplayMode.CompactList; public List Children { get; set; } = []; diff --git a/README.md b/README.md index 358006c..1a077c8 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Download the compiled Windows release from: - Nested menus, drag/drop ordering, and drag/drop item creation in Settings - Right-click launcher entries to edit them in Settings - Right-click item duplication in Settings +- Right-click items in Settings to pin them to the top of their menu - Per-item icon and display mode - Automatic icon selection from app/folder targets - Portable `config.json` diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs index 160abf1..39eeb18 100644 --- a/Services/ConfigService.cs +++ b/Services/ConfigService.cs @@ -240,6 +240,7 @@ public static class ConfigService config.RecentItems.RemoveAll(item => item.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(item.Target)); foreach (LauncherItem item in config.RecentItems) { + item.IsFavorite = false; item.Children = []; NormalizeDisplayModes(item); } diff --git a/Services/LauncherController.cs b/Services/LauncherController.cs index 6832271..bc70aa6 100644 --- a/Services/LauncherController.cs +++ b/Services/LauncherController.cs @@ -181,6 +181,7 @@ public sealed class LauncherController : IDisposable Target = item.Target, Arguments = item.Arguments, IconPath = item.IconPath, + IsFavorite = false, DisplayMode = item.DisplayMode, Children = [] }; diff --git a/TaskbarLauncher.csproj b/TaskbarLauncher.csproj index b942def..1262538 100644 --- a/TaskbarLauncher.csproj +++ b/TaskbarLauncher.csproj @@ -12,8 +12,8 @@ Ray Berezowski Assets\AppIcon.ico 1.0.1 - 1.0.1.5 - 1.0.1.5 + 1.0.1.6 + 1.0.1.6 1.0.1 diff --git a/Views/LauncherPopup.xaml.cs b/Views/LauncherPopup.xaml.cs index f9224e3..ce5a6b5 100644 --- a/Views/LauncherPopup.xaml.cs +++ b/Views/LauncherPopup.xaml.cs @@ -100,7 +100,7 @@ public partial class LauncherPopup : Window } bool expandMenusByDefault = config.Items.Count <= 2; - foreach (LauncherItem item in config.Items) + foreach (LauncherItem item in GetDisplayItems(config.Items)) { ItemsPanel.Children.Add(CreateItemControl(item, 0, expandMenusByDefault)); } @@ -137,7 +137,7 @@ public partial class LauncherPopup : Window }; StackPanel children = new(); - foreach (LauncherItem child in item.Children) + foreach (LauncherItem child in GetDisplayItems(item.Children)) { children.Children.Add(CreateItemControl(child, depth + 1, expandMenusByDefault, allowEdit)); } @@ -231,6 +231,11 @@ public partial class LauncherPopup : Window return item.Type != LauncherItemType.Menu && !string.IsNullOrWhiteSpace(item.Target); } + private static IEnumerable GetDisplayItems(IEnumerable items) + { + return items.OrderByDescending(item => item.IsFavorite); + } + private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) { string query = SearchBox.Text.Trim(); @@ -273,7 +278,7 @@ public partial class LauncherPopup : Window private static void CollectMatches(IEnumerable items, string query, List matches) { - foreach (LauncherItem item in items) + foreach (LauncherItem item in GetDisplayItems(items)) { bool isLaunchable = item.Type != LauncherItemType.Menu; if (isLaunchable && Matches(item, query)) diff --git a/config.example.json b/config.example.json index 316c610..473ac12 100644 --- a/config.example.json +++ b/config.example.json @@ -10,6 +10,7 @@ "Target": "", "Arguments": null, "IconPath": null, + "IsFavorite": false, "DisplayMode": "CompactList", "Children": [ { @@ -18,6 +19,7 @@ "Target": "%USERPROFILE%\\Documents", "Arguments": null, "IconPath": "%USERPROFILE%\\Documents", + "IsFavorite": false, "DisplayMode": "CompactList", "Children": [] }, @@ -27,6 +29,7 @@ "Target": "%USERPROFILE%\\Downloads", "Arguments": null, "IconPath": "%USERPROFILE%\\Downloads", + "IsFavorite": false, "DisplayMode": "CompactList", "Children": [] } @@ -38,6 +41,7 @@ "Target": "", "Arguments": null, "IconPath": null, + "IsFavorite": false, "DisplayMode": "CompactList", "Children": [ { @@ -46,6 +50,7 @@ "Target": "notepad.exe", "Arguments": null, "IconPath": "notepad.exe", + "IsFavorite": false, "DisplayMode": "CompactList", "Children": [] } diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 1080cf7..3eedfd8 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -35,6 +35,7 @@ From Settings you can: - Drag/drop items to reorder - Drag/drop files, folders, shortcuts, or web links into the launcher item list - Right-click an item and choose **Duplicate Item** to copy it +- Right-click an item and choose **Pin to Top** to show it first in its menu - Set per-item icon sizes - Back up and restore `config.json` - Enable Start with Windows