Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion RetroBar/Controls/NotifyIconList.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RetroBar.Controls"
xmlns:converters="clr-namespace:RetroBar.Converters"
xmlns:dd="urn:gong-wpf-dragdrop"
Loaded="NotifyIconList_Loaded"
Unloaded="NotifyIconList_OnUnloaded">
<UserControl.Resources>
Expand All @@ -23,7 +24,10 @@
Style="{DynamicResource TrayToggleButton}"/>
<ItemsControl x:Name="NotifyIcons"
Focusable="False"
Style="{DynamicResource NotifyIconItems}">
Style="{DynamicResource NotifyIconItems}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DragDropContext="NotifyIconList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
Expand Down
98 changes: 77 additions & 21 deletions RetroBar/Controls/NotifyIconList.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));

Expand All @@ -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))
Expand All @@ -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;
}
}
}
Expand All @@ -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)
Expand All @@ -100,7 +95,7 @@ private void SetNotificationAreaCollections()
}
else
{
NotifyIcons.ItemsSource = allNotifyIconsSource.View;
NotifyIcons.ItemsSource = allNotifyIcons;
}

_isLoaded = true;
Expand Down Expand Up @@ -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)
Expand All @@ -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();
Copy link

Copilot AI Aug 21, 2025

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.

Suggested change
var sortedIcons = icons.OrderBy(i => Settings.Instance.NotifyIconOrder.IndexOf(i.GetInvertIdentifier())).ToList();
var sortedIcons = icons.OrderBy(i =>
{
int idx = Settings.Instance.NotifyIconOrder.IndexOf(i.GetInvertIdentifier());
return idx == -1 ? Settings.Instance.NotifyIconOrder.Count : idx;
}).ToList();

Copilot uses AI. Check for mistakes.

// 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);
Copy link

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same IndexOf issue as line 218. Missing icons from the saved order will all receive -1 and be grouped together at the beginning, losing their relative order.

Suggested change
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 uses AI. Check for mistakes.
}
}

private void NotifyIconToggleButton_OnClick(object sender, RoutedEventArgs e)
{
if (NotifyIconToggleButton.IsChecked == true)
{

NotifyIcons.ItemsSource = allNotifyIconsSource.View;
NotifyIcons.ItemsSource = allNotifyIcons;
}
else
{
NotifyIcons.ItemsSource = pinnedNotifyIconsSource.View;
NotifyIcons.ItemsSource = pinnedNotifyIcons;
}
}

Expand All @@ -230,5 +266,25 @@ private void SetToggleVisibility()
NotifyIconToggleButton.Visibility = Visibility.Visible;
}
}

public void SaveIconOrder()
{
var visibleIcons = new System.Collections.Generic.List<ManagedShell.WindowsTray.NotifyIcon>();
Copy link

Copilot AI Aug 21, 2025

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.

Suggested change
var visibleIcons = new System.Collections.Generic.List<ManagedShell.WindowsTray.NotifyIcon>();
var visibleIcons = new List<ManagedShell.WindowsTray.NotifyIcon>();

Copilot uses AI. Check for mistakes.

if (NotifyIcons.ItemsSource != null)
{
foreach (var item in NotifyIcons.ItemsSource)
{
if (item is ManagedShell.WindowsTray.NotifyIcon icon)
{
visibleIcons.Add(icon);
}
}
}

// Save the order to settings
var identifiers = visibleIcons.Select(icon => icon.GetInvertIdentifier()).ToList();
Settings.Instance.NotifyIconOrder = identifiers;
}
}
}
47 changes: 47 additions & 0 deletions RetroBar/Utilities/NotifyIconDropHandler.cs
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;
}
}
}
7 changes: 7 additions & 0 deletions RetroBar/Utilities/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ public List<NotifyIconBehaviorSetting> NotifyIconBehaviors
set => Set(ref _notifyIconBehaviors, value);
}

private List<string> _notifyIconOrder = new List<string>();
public List<string> NotifyIconOrder
{
get => _notifyIconOrder;
set => Set(ref _notifyIconOrder, value);
}

private bool _allowFontSmoothing = false;
public bool AllowFontSmoothing
{
Expand Down
Loading