-
Notifications
You must be signed in to change notification settings - Fork 311
Add support for reordering notification icons via drag-and-drop #1227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,9 +2,9 @@ | |||||||||||||||
| using System.Collections.ObjectModel; | ||||||||||||||||
| using System.Collections.Specialized; | ||||||||||||||||
| using System.ComponentModel; | ||||||||||||||||
| using System.Linq; | ||||||||||||||||
| using System.Windows; | ||||||||||||||||
| using System.Windows.Controls; | ||||||||||||||||
| using System.Windows.Data; | ||||||||||||||||
| using System.Windows.Threading; | ||||||||||||||||
| using ManagedShell.WindowsTray; | ||||||||||||||||
| using RetroBar.Extensions; | ||||||||||||||||
|
|
@@ -18,9 +18,10 @@ namespace RetroBar.Controls | |||||||||||||||
| public partial class NotifyIconList : UserControl | ||||||||||||||||
| { | ||||||||||||||||
| private bool _isLoaded; | ||||||||||||||||
| private CollectionViewSource allNotifyIconsSource; | ||||||||||||||||
| private CollectionViewSource pinnedNotifyIconsSource; | ||||||||||||||||
| private readonly ObservableCollection<ManagedShell.WindowsTray.NotifyIcon> allNotifyIcons = new ObservableCollection<ManagedShell.WindowsTray.NotifyIcon>(); | ||||||||||||||||
| private readonly ObservableCollection<ManagedShell.WindowsTray.NotifyIcon> pinnedNotifyIcons = new ObservableCollection<ManagedShell.WindowsTray.NotifyIcon>(); | ||||||||||||||||
| private ObservableCollection<ManagedShell.WindowsTray.NotifyIcon> promotedIcons = new ObservableCollection<ManagedShell.WindowsTray.NotifyIcon>(); | ||||||||||||||||
| private NotifyIconDropHandler dropHandler; | ||||||||||||||||
|
|
||||||||||||||||
| public static DependencyProperty NotificationAreaProperty = DependencyProperty.Register(nameof(NotificationArea), typeof(NotificationArea), typeof(NotifyIconList), new PropertyMetadata(NotificationAreaChangedCallback)); | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -41,15 +42,15 @@ private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) | |||||||||||||||
| { | ||||||||||||||||
| if (Settings.Instance.CollapseNotifyIcons) | ||||||||||||||||
| { | ||||||||||||||||
| NotifyIcons.ItemsSource = pinnedNotifyIconsSource.View; | ||||||||||||||||
| NotifyIcons.ItemsSource = pinnedNotifyIcons; | ||||||||||||||||
| SetToggleVisibility(); | ||||||||||||||||
| } | ||||||||||||||||
| else | ||||||||||||||||
| { | ||||||||||||||||
| NotifyIconToggleButton.IsChecked = false; | ||||||||||||||||
| NotifyIconToggleButton.Visibility = Visibility.Collapsed; | ||||||||||||||||
|
|
||||||||||||||||
| NotifyIcons.ItemsSource = allNotifyIconsSource.View; | ||||||||||||||||
| NotifyIcons.ItemsSource = allNotifyIcons; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| else if (e.PropertyName == nameof(Settings.InvertIconsMode) || e.PropertyName == nameof(Settings.InvertNotifyIcons)) | ||||||||||||||||
|
|
@@ -59,11 +60,11 @@ private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) | |||||||||||||||
|
|
||||||||||||||||
| if (Settings.Instance.CollapseNotifyIcons && NotifyIconToggleButton.IsChecked != true) | ||||||||||||||||
| { | ||||||||||||||||
| NotifyIcons.ItemsSource = pinnedNotifyIconsSource.View; | ||||||||||||||||
| NotifyIcons.ItemsSource = pinnedNotifyIcons; | ||||||||||||||||
| } | ||||||||||||||||
| else | ||||||||||||||||
| { | ||||||||||||||||
| NotifyIcons.ItemsSource = allNotifyIconsSource.View; | ||||||||||||||||
| NotifyIcons.ItemsSource = allNotifyIcons; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -72,25 +73,19 @@ private void SetNotificationAreaCollections() | |||||||||||||||
| { | ||||||||||||||||
| if (!_isLoaded && NotificationArea != null) | ||||||||||||||||
| { | ||||||||||||||||
| CompositeCollection allNotifyIcons = new CompositeCollection(); | ||||||||||||||||
| allNotifyIcons.Add(new CollectionContainer { Collection = NotificationArea.UnpinnedIcons }); | ||||||||||||||||
| allNotifyIcons.Add(new CollectionContainer { Collection = NotificationArea.PinnedIcons }); | ||||||||||||||||
| allNotifyIconsSource = new CollectionViewSource { Source = allNotifyIcons }; | ||||||||||||||||
| RefreshCollections(); | ||||||||||||||||
|
|
||||||||||||||||
| NotificationArea.UnpinnedIcons.Filter = UnpinnedNotifyIcons_Filter; | ||||||||||||||||
|
|
||||||||||||||||
| CompositeCollection pinnedNotifyIcons = new CompositeCollection(); | ||||||||||||||||
| pinnedNotifyIcons.Add(new CollectionContainer { Collection = promotedIcons }); | ||||||||||||||||
| pinnedNotifyIcons.Add(new CollectionContainer { Collection = NotificationArea.PinnedIcons }); | ||||||||||||||||
| pinnedNotifyIconsSource = new CollectionViewSource { Source = pinnedNotifyIcons }; | ||||||||||||||||
|
|
||||||||||||||||
| NotificationArea.UnpinnedIcons.CollectionChanged += UnpinnedIcons_CollectionChanged; | ||||||||||||||||
| NotificationArea.PinnedIcons.CollectionChanged += PinnedIcons_CollectionChanged; | ||||||||||||||||
| NotificationArea.NotificationBalloonShown += NotificationArea_NotificationBalloonShown; | ||||||||||||||||
|
|
||||||||||||||||
| Settings.Instance.PropertyChanged += Settings_PropertyChanged; | ||||||||||||||||
|
|
||||||||||||||||
| if (Settings.Instance.CollapseNotifyIcons) | ||||||||||||||||
| { | ||||||||||||||||
| NotifyIcons.ItemsSource = pinnedNotifyIconsSource.View; | ||||||||||||||||
| NotifyIcons.ItemsSource = pinnedNotifyIcons; | ||||||||||||||||
| SetToggleVisibility(); | ||||||||||||||||
|
|
||||||||||||||||
| if (NotifyIconToggleButton.IsChecked == true) | ||||||||||||||||
|
|
@@ -100,7 +95,7 @@ private void SetNotificationAreaCollections() | |||||||||||||||
| } | ||||||||||||||||
| else | ||||||||||||||||
| { | ||||||||||||||||
| NotifyIcons.ItemsSource = allNotifyIconsSource.View; | ||||||||||||||||
| NotifyIcons.ItemsSource = allNotifyIcons; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| _isLoaded = true; | ||||||||||||||||
|
|
@@ -174,6 +169,10 @@ private void NotificationArea_NotificationBalloonShown(object sender, Notificati | |||||||||||||||
| private void NotifyIconList_Loaded(object sender, RoutedEventArgs e) | ||||||||||||||||
| { | ||||||||||||||||
| SetNotificationAreaCollections(); | ||||||||||||||||
|
|
||||||||||||||||
| // Set up drag/drop handler | ||||||||||||||||
| dropHandler = new NotifyIconDropHandler(this); | ||||||||||||||||
| GongSolutions.Wpf.DragDrop.DragDrop.SetDropHandler(NotifyIcons, dropHandler); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private void NotifyIconList_OnUnloaded(object sender, RoutedEventArgs e) | ||||||||||||||||
|
|
@@ -197,18 +196,55 @@ private void NotifyIconList_OnUnloaded(object sender, RoutedEventArgs e) | |||||||||||||||
| private void UnpinnedIcons_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) | ||||||||||||||||
| { | ||||||||||||||||
| SetToggleVisibility(); | ||||||||||||||||
| RefreshCollections(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private void PinnedIcons_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) | ||||||||||||||||
| { | ||||||||||||||||
| RefreshCollections(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private void RefreshCollections() | ||||||||||||||||
| { | ||||||||||||||||
| if (NotificationArea == null) return; | ||||||||||||||||
|
|
||||||||||||||||
| // Create a list of all icons | ||||||||||||||||
| var icons = NotificationArea.UnpinnedIcons.Cast<ManagedShell.WindowsTray.NotifyIcon>() | ||||||||||||||||
| .Where(icon => !icon.IsPinned && !icon.IsHidden && icon.GetBehavior() != NotifyIconBehavior.Remove) | ||||||||||||||||
| .Union(NotificationArea.PinnedIcons.Cast<ManagedShell.WindowsTray.NotifyIcon>()) | ||||||||||||||||
| .ToList(); | ||||||||||||||||
|
|
||||||||||||||||
| // Sort icons according to saved order | ||||||||||||||||
| var sortedIcons = icons.OrderBy(i => Settings.Instance.NotifyIconOrder.IndexOf(i.GetInvertIdentifier())).ToList(); | ||||||||||||||||
|
|
||||||||||||||||
| // Refresh all icons collection | ||||||||||||||||
| allNotifyIcons.Clear(); | ||||||||||||||||
| foreach (var icon in sortedIcons) | ||||||||||||||||
| { | ||||||||||||||||
| allNotifyIcons.Add(icon); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Refresh pinned icons collection | ||||||||||||||||
| pinnedNotifyIcons.Clear(); | ||||||||||||||||
| foreach (var icon in promotedIcons) | ||||||||||||||||
| { | ||||||||||||||||
| pinnedNotifyIcons.Add(icon); | ||||||||||||||||
| } | ||||||||||||||||
| foreach (var icon in NotificationArea.PinnedIcons.Cast<ManagedShell.WindowsTray.NotifyIcon>().OrderBy(i => Settings.Instance.NotifyIconOrder.IndexOf(i.GetInvertIdentifier()))) | ||||||||||||||||
| { | ||||||||||||||||
| pinnedNotifyIcons.Add(icon); | ||||||||||||||||
|
||||||||||||||||
| pinnedNotifyIcons.Add(icon); | |
| foreach (var tuple in NotificationArea.PinnedIcons.Cast<ManagedShell.WindowsTray.NotifyIcon>() | |
| .Select((icon, idx) => new { icon, idx, orderIndex = Settings.Instance.NotifyIconOrder.IndexOf(icon.GetInvertIdentifier()) }) | |
| .OrderBy(t => t.orderIndex == -1 ? int.MaxValue : t.orderIndex) | |
| .ThenBy(t => t.idx)) | |
| { | |
| pinnedNotifyIcons.Add(tuple.icon); |
Copilot
AI
Aug 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the fully qualified type name 'System.Collections.Generic.List' is unnecessary since 'using System.Collections.Generic;' is already present. Use 'List<ManagedShell.WindowsTray.NotifyIcon>' instead.
| var visibleIcons = new System.Collections.Generic.List<ManagedShell.WindowsTray.NotifyIcon>(); | |
| var visibleIcons = new List<ManagedShell.WindowsTray.NotifyIcon>(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| using GongSolutions.Wpf.DragDrop; | ||
| using RetroBar.Controls; | ||
|
|
||
| namespace RetroBar.Utilities | ||
| { | ||
| public class NotifyIconDropHandler : IDropTarget | ||
| { | ||
| private NotifyIconList _notifyIconList; | ||
|
|
||
| public IDropInfo DropInFlight { get; set; } | ||
|
|
||
| public NotifyIconDropHandler(NotifyIconList notifyIconList) | ||
| { | ||
| _notifyIconList = notifyIconList; | ||
| } | ||
|
|
||
| void IDropTarget.DragOver(IDropInfo dropInfo) | ||
| { | ||
| DragDrop.DefaultDropHandler.DragOver(dropInfo); | ||
| } | ||
|
|
||
| #if !NETCOREAPP3_1_OR_GREATER | ||
| public void DragEnter(IDropInfo dropInfo) | ||
| { | ||
| DragDrop.DefaultDropHandler.DragEnter(dropInfo); | ||
| } | ||
|
|
||
| public void DragLeave(IDropInfo dropInfo) | ||
| { | ||
| DragDrop.DefaultDropHandler.DragLeave(dropInfo); | ||
| } | ||
| #endif | ||
|
|
||
| void IDropTarget.Drop(IDropInfo dropInfo) | ||
| { | ||
| // Save before the drop in order to catch any items not yet saved | ||
| _notifyIconList.SaveIconOrder(); | ||
| DropInFlight = dropInfo; | ||
|
|
||
| DragDrop.DefaultDropHandler.Drop(dropInfo); | ||
|
|
||
| // Save post-drop state | ||
| _notifyIconList.SaveIconOrder(); | ||
| DropInFlight = null; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using IndexOf for ordering will fail when icons are not in the saved order list. IndexOf returns -1 for missing items, which will cause all unknown icons to be sorted to the beginning with the same priority. Use a custom ordering logic that handles missing items by assigning them a default order value.