From 50f02ed247219f7a620b3610f5b73c876023d11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Fri, 24 Jul 2026 23:19:13 -0500 Subject: [PATCH 1/8] Add grouped tasks support --- RetroBar/App.xaml.cs | 5 +- RetroBar/Controls/TaskButton.xaml | 101 ++++-- RetroBar/Controls/TaskButton.xaml.cs | 323 ++++++++++++++++-- RetroBar/Controls/TaskList.xaml | 28 +- RetroBar/Controls/TaskList.xaml.cs | 75 +++- RetroBar/Controls/TaskThumbnail.xaml.cs | 42 ++- .../TaskGroupVisibilityConverter.cs | 31 ++ RetroBar/Converters/TaskLabelConverter.cs | 50 ++- .../Converters/TaskThumbnailItemsConverter.cs | 37 ++ RetroBar/Converters/TaskToolTipConverter.cs | 56 +++ .../Converters/ToolTipPlacementConverter.cs | 6 +- RetroBar/Languages/English.xaml | 4 + "RetroBar/Languages/espa\303\261ol.xaml" | 4 + RetroBar/PropertiesWindow.xaml | 4 + RetroBar/Utilities/Settings.cs | 7 + RetroBar/Utilities/TaskCategoryProvider.cs | 26 ++ RetroBar/Utilities/TaskDropHandler.cs | 134 ++++++++ 17 files changed, 860 insertions(+), 73 deletions(-) create mode 100644 RetroBar/Converters/TaskGroupVisibilityConverter.cs create mode 100644 RetroBar/Converters/TaskThumbnailItemsConverter.cs create mode 100644 RetroBar/Converters/TaskToolTipConverter.cs create mode 100644 RetroBar/Utilities/TaskCategoryProvider.cs create mode 100644 RetroBar/Utilities/TaskDropHandler.cs diff --git a/RetroBar/App.xaml.cs b/RetroBar/App.xaml.cs index 76290d29..3ead6e7e 100644 --- a/RetroBar/App.xaml.cs +++ b/RetroBar/App.xaml.cs @@ -115,8 +115,11 @@ private ShellManager SetupManagedShell() ShellConfig config = ShellManager.DefaultShellConfig; config.PinnedNotifyIcons = Settings.Instance.NotifyIconBehaviors.Where(setting => setting.Behavior == NotifyIconBehavior.AlwaysShow).Select(setting => setting.Identifier).ToArray(); + config.AutoStartTasksService = false; - return new ShellManager(config); + ShellManager manager = new ShellManager(config); + manager.Tasks.Initialize(new TaskCategoryProvider(), config.MultiMonAwareTasksService); + return manager; } public void RestartApp() diff --git a/RetroBar/Controls/TaskButton.xaml b/RetroBar/Controls/TaskButton.xaml index 6eef4e63..8fe387f2 100644 --- a/RetroBar/Controls/TaskButton.xaml +++ b/RetroBar/Controls/TaskButton.xaml @@ -4,6 +4,7 @@ xmlns:converters="clr-namespace:RetroBar.Converters" xmlns:controls="clr-namespace:RetroBar.Controls" xmlns:utilities="clr-namespace:RetroBar.Utilities" + xmlns:managedShellTasks="clr-namespace:ManagedShell.WindowsTasks;assembly=ManagedShell.WindowsTasks" Loaded="TaskButton_OnLoaded" Unloaded="TaskButton_OnUnloaded"> @@ -12,8 +13,12 @@ + + + diff --git a/RetroBar/Controls/TaskButton.xaml.cs b/RetroBar/Controls/TaskButton.xaml.cs index 3a6f28d9..e5c99a4f 100644 --- a/RetroBar/Controls/TaskButton.xaml.cs +++ b/RetroBar/Controls/TaskButton.xaml.cs @@ -1,6 +1,10 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -33,6 +37,16 @@ public TaskList Host private DelayedActivationHandler dragHandler; private bool _isLoaded; + private INotifyCollectionChanged _subscribedCollection; + private List _subscribedWindows = new List(); + + public static readonly DependencyProperty TasksProperty = DependencyProperty.Register("Tasks", typeof(IEnumerable), typeof(TaskButton)); + + public IEnumerable Tasks + { + get => (IEnumerable)GetValue(TasksProperty); + set => SetValue(TasksProperty, value); + } public TaskButton() { @@ -42,13 +56,41 @@ public TaskButton() private void SetStyle() { - MultiBinding multiBinding = new MultiBinding(); - multiBinding.Converter = StyleConverter; + ApplicationWindow.WindowState state = ApplicationWindow.WindowState.Inactive; + + if (Tasks != null) + { + var windows = Tasks.OfType().ToList(); + if (windows.Any(w => w.State == ApplicationWindow.WindowState.Active)) + { + state = ApplicationWindow.WindowState.Active; + } + else if (windows.Any(w => w.State == ApplicationWindow.WindowState.Flashing)) + { + state = ApplicationWindow.WindowState.Flashing; + } + } + else if (Window != null) + { + state = Window.State; + } + + var fxStyle = this.FindResource("TaskButton") as Style; + if (state == ApplicationWindow.WindowState.Active) + { + fxStyle = this.FindResource("TaskButtonActive") as Style; + } + else if (state == ApplicationWindow.WindowState.Flashing) + { + fxStyle = this.FindResource("TaskButtonFlashing") as Style; + } - multiBinding.Bindings.Add(new Binding { RelativeSource = RelativeSource.Self }); - multiBinding.Bindings.Add(new Binding("State")); + if (AppButton.ContextMenu?.IsOpen == true) + { + fxStyle = this.FindResource("TaskButtonActive") as Style; + } - AppButton.SetBinding(StyleProperty, multiBinding); + AppButton.Style = fxStyle; } private void ScrollIntoView() @@ -58,7 +100,21 @@ private void ScrollIntoView() return; } - if (Window.State == ApplicationWindow.WindowState.Active) + ApplicationWindow.WindowState state = ApplicationWindow.WindowState.Inactive; + if (Tasks != null) + { + var windows = Tasks.OfType().ToList(); + if (windows.Any(w => w.State == ApplicationWindow.WindowState.Active)) + { + state = ApplicationWindow.WindowState.Active; + } + } + else + { + state = Window.State; + } + + if (state == ApplicationWindow.WindowState.Active) { BringIntoView(); } @@ -85,7 +141,42 @@ private void Animate() private void TaskButton_OnLoaded(object sender, RoutedEventArgs e) { - Window = DataContext as ApplicationWindow; + if (DataContext is CollectionViewGroup group) + { + Window = group.Items.Count > 0 ? group.Items[0] as ApplicationWindow : null; + AppButton.DataContext = Window; + + if (group.Items is INotifyCollectionChanged collectionChanged) + { + _subscribedCollection = collectionChanged; + _subscribedCollection.CollectionChanged -= GroupItems_CollectionChanged; + _subscribedCollection.CollectionChanged += GroupItems_CollectionChanged; + } + foreach (ApplicationWindow w in group.Items) + { + if (!_subscribedWindows.Contains(w)) + { + w.PropertyChanged -= Window_PropertyChanged; + w.PropertyChanged += Window_PropertyChanged; + w.GetButtonRect -= Window_GetButtonRect; + w.GetButtonRect += Window_GetButtonRect; + _subscribedWindows.Add(w); + } + } + } + else + { + Window = DataContext as ApplicationWindow; + AppButton.DataContext = Window; + if (Window != null && !_subscribedWindows.Contains(Window)) + { + Window.PropertyChanged -= Window_PropertyChanged; + Window.PropertyChanged += Window_PropertyChanged; + Window.GetButtonRect -= Window_GetButtonRect; + Window.GetButtonRect += Window_GetButtonRect; + _subscribedWindows.Add(Window); + } + } Settings.Instance.PropertyChanged += Settings_PropertyChanged; @@ -94,18 +185,67 @@ private void TaskButton_OnLoaded(object sender, RoutedEventArgs e) Window?.BringToFront(); }); - if (Window != null) + if (Settings.Instance.SlideTaskbarButtons && Host?.Host?.Orientation == Orientation.Horizontal) { - Window.GetButtonRect += Window_GetButtonRect; - Window.PropertyChanged += Window_PropertyChanged; + Animate(); } - if (Settings.Instance.SlideTaskbarButtons && Host?.Host?.Orientation == Orientation.Horizontal) + if (AppButton.ToolTip is ToolTip toolTip) { - Animate(); + toolTip.CustomPopupPlacementCallback = new System.Windows.Controls.Primitives.CustomPopupPlacementCallback(ToolTipCustomPlacement); } _isLoaded = true; + SetStyle(); + } + + private System.Windows.Controls.Primitives.CustomPopupPlacement[] ToolTipCustomPlacement(Size popupSize, Size targetSize, Point offset) + { + double x = (targetSize.Width - popupSize.Width) / 2.0; + double y = -popupSize.Height - 5; + System.Windows.Controls.Primitives.PopupPrimaryAxis axis = System.Windows.Controls.Primitives.PopupPrimaryAxis.Horizontal; + + if (Settings.Instance.Edge == ManagedShell.AppBar.AppBarEdge.Top) + { + y = targetSize.Height + 5; + } + + return new System.Windows.Controls.Primitives.CustomPopupPlacement[] { + new System.Windows.Controls.Primitives.CustomPopupPlacement(new Point(x, y), axis) + }; + } + + private void GroupItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (ApplicationWindow w in e.OldItems) + { + w.PropertyChanged -= Window_PropertyChanged; + w.GetButtonRect -= Window_GetButtonRect; + _subscribedWindows.Remove(w); + } + } + if (e.NewItems != null) + { + foreach (ApplicationWindow w in e.NewItems) + { + if (!_subscribedWindows.Contains(w)) + { + w.PropertyChanged += Window_PropertyChanged; + w.GetButtonRect += Window_GetButtonRect; + _subscribedWindows.Add(w); + } + } + } + // Update the main Window binding to point to the remaining active window + if (DataContext is CollectionViewGroup group) + { + Window = group.Items.Count > 0 ? group.Items[0] as ApplicationWindow : null; + AppButton.DataContext = Window; + } + + SetStyle(); } private void Window_GetButtonRect(ref NativeMethods.ShortRect rect) @@ -129,6 +269,7 @@ private void Window_PropertyChanged(object sender, PropertyChangedEventArgs e) if (e.PropertyName == "State") { ScrollIntoView(); + SetStyle(); } } @@ -142,17 +283,61 @@ private void TaskButton_OnUnloaded(object sender, RoutedEventArgs e) Settings.Instance.PropertyChanged -= Settings_PropertyChanged; dragHandler?.Dispose(); - if (Window != null) + if (_subscribedCollection != null) + { + _subscribedCollection.CollectionChanged -= GroupItems_CollectionChanged; + _subscribedCollection = null; + } + + foreach (ApplicationWindow w in _subscribedWindows) { - Window.GetButtonRect -= Window_GetButtonRect; - Window.PropertyChanged -= Window_PropertyChanged; + w.GetButtonRect -= Window_GetButtonRect; + w.PropertyChanged -= Window_PropertyChanged; } + _subscribedWindows.Clear(); _isLoaded = false; } private void AppButton_OnContextMenuOpening(object sender, ContextMenuEventArgs e) { + var windows = Tasks?.OfType().ToList(); + bool isGroup = windows != null && windows.Count > 1; + + if (isGroup) + { + RestoreMenuItem.Visibility = Visibility.Collapsed; + MoveMenuItem.Visibility = Visibility.Collapsed; + SizeMenuItem.Visibility = Visibility.Collapsed; + MinimizeMenuItem.Visibility = Visibility.Collapsed; + MaximizeMenuItem.Visibility = Visibility.Collapsed; + EndTaskMenuItem.Visibility = Visibility.Collapsed; + CloseMenuItem.Visibility = Visibility.Collapsed; + SingleSeparator.Visibility = Visibility.Collapsed; + + MinimizeGroupMenuItem.Visibility = Visibility.Visible; + GroupSeparator.Visibility = Visibility.Visible; + CloseGroupMenuItem.Visibility = Visibility.Visible; + + MinimizeGroupMenuItem.IsEnabled = windows.Any(w => w.CanMinimize && w.ShowStyle != NativeMethods.WindowShowStyle.ShowMinimized); + CloseGroupMenuItem.IsEnabled = true; + CloseGroupMenuItem.FontWeight = FontWeights.Normal; + return; + } + + MinimizeGroupMenuItem.Visibility = Visibility.Collapsed; + GroupSeparator.Visibility = Visibility.Collapsed; + CloseGroupMenuItem.Visibility = Visibility.Collapsed; + + RestoreMenuItem.Visibility = Visibility.Visible; + MoveMenuItem.Visibility = Visibility.Visible; + SizeMenuItem.Visibility = Visibility.Visible; + MinimizeMenuItem.Visibility = Visibility.Visible; + MaximizeMenuItem.Visibility = Visibility.Visible; + EndTaskMenuItem.Visibility = Visibility.Visible; + CloseMenuItem.Visibility = Visibility.Visible; + SingleSeparator.Visibility = Visibility.Visible; + if (Window == null) { return; @@ -178,6 +363,32 @@ private void AppButton_OnContextMenuOpening(object sender, ContextMenuEventArgs SizeMenuItem.IsEnabled = wss == NativeMethods.WindowShowStyle.ShowNormal && (ws & (int)NativeMethods.WindowStyles.WS_MAXIMIZEBOX) != 0; } + private void MinimizeGroupMenuItem_OnClick(object sender, RoutedEventArgs e) + { + if (Tasks != null) + { + foreach (ApplicationWindow win in Tasks.OfType()) + { + if (win.CanMinimize) + { + win.Minimize(); + } + } + } + } + + private void CloseGroupMenuItem_OnClick(object sender, RoutedEventArgs e) + { + if (Tasks != null) + { + var windows = Tasks.OfType().ToList(); + foreach (ApplicationWindow win in windows) + { + win.Close(); + } + } + } + private void CloseMenuItem_OnClick(object sender, RoutedEventArgs e) { Window?.Close(); @@ -242,13 +453,87 @@ private void MaximizeMenuItem_OnClick(object sender, RoutedEventArgs e) private void AppButton_OnClick(object sender, RoutedEventArgs e) { - if (PressedWindowState == ApplicationWindow.WindowState.Active && Window?.CanMinimize == true) + if (Tasks != null) { - Window?.Minimize(); + var windows = Tasks.OfType().ToList(); + if (windows.Count > 1) + { + ContextMenu groupMenu = new ContextMenu(); + + foreach (var window in windows) + { + MenuItem menuItem = new MenuItem(); + menuItem.Header = window.Title; + + if (window.Icon != null) + { + Image icon = new Image(); + icon.Source = window.Icon; + icon.Width = 16; + icon.Height = 16; + menuItem.Icon = icon; + } + + // We need a local copy of the window reference for the closure + var localWindow = window; + menuItem.Click += (s, ev) => + { + if (localWindow.State == ApplicationWindow.WindowState.Active && localWindow.CanMinimize) + { + localWindow.Minimize(); + } + else + { + localWindow.BringToFront(); + } + }; + groupMenu.Items.Add(menuItem); + } + + groupMenu.PlacementTarget = AppButton; + + if (Settings.Instance.Edge == ManagedShell.AppBar.AppBarEdge.Top) + groupMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom; + else if (Settings.Instance.Edge == ManagedShell.AppBar.AppBarEdge.Left) + groupMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Right; + else if (Settings.Instance.Edge == ManagedShell.AppBar.AppBarEdge.Right) + groupMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Left; + else + groupMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Top; + + // Bind TextRenderingMode to match other menus if possible + Binding textRenderingBinding = new Binding("AllowFontSmoothingMenu"); + textRenderingBinding.Source = Settings.Instance; + textRenderingBinding.Converter = FindResource("menuTextRenderingModeConverter") as IValueConverter; + if (textRenderingBinding.Converter != null) + { + groupMenu.SetBinding(System.Windows.Media.TextOptions.TextRenderingModeProperty, textRenderingBinding); + } + + groupMenu.IsOpen = true; + return; + } + } + + // Fallback for single windows or ungrouped task buttons + ApplicationWindow targetWindow = Window; + + if (Tasks != null) + { + var windows = Tasks.OfType().ToList(); + if (windows.Count == 1) + { + targetWindow = windows[0]; + } + } + + if (PressedWindowState == ApplicationWindow.WindowState.Active && targetWindow?.CanMinimize == true) + { + targetWindow?.Minimize(); } else { - Window?.BringToFront(); + targetWindow?.BringToFront(); } } @@ -300,7 +585,7 @@ private void AppButton_OnDragLeave(object sender, DragEventArgs e) private void ContextMenu_OpenedOrClosed(object sender, RoutedEventArgs e) { - BindingOperations.GetMultiBindingExpression(AppButton, StyleProperty).UpdateTarget(); + SetStyle(); } } } \ No newline at end of file diff --git a/RetroBar/Controls/TaskList.xaml b/RetroBar/Controls/TaskList.xaml index 3747412d..98b45ebd 100644 --- a/RetroBar/Controls/TaskList.xaml +++ b/RetroBar/Controls/TaskList.xaml @@ -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:managedShellTasks="clr-namespace:ManagedShell.WindowsTasks;assembly=ManagedShell.WindowsTasks" xmlns:dd="urn:gong-wpf-dragdrop" Loaded="TaskList_OnLoaded" Unloaded="TaskList_OnUnloaded" @@ -18,9 +19,9 @@ + ConverterParameter="leading" + Path="Orientation" + RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Window}" /> @@ -45,8 +47,20 @@ - - + + + + + + + + + + + + + + @@ -57,8 +71,8 @@ - - + + diff --git a/RetroBar/Controls/TaskList.xaml.cs b/RetroBar/Controls/TaskList.xaml.cs index fd23ec5b..05321ae1 100644 --- a/RetroBar/Controls/TaskList.xaml.cs +++ b/RetroBar/Controls/TaskList.xaml.cs @@ -3,7 +3,9 @@ using ManagedShell.Common.Helpers; using RetroBar.Utilities; using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Windows; using System.Windows.Controls; @@ -21,6 +23,7 @@ public partial class TaskList : UserControl private double TaskButtonLeftMargin; private double TaskButtonRightMargin; private ICollectionView taskbarItems; + internal System.Collections.IList UnderlyingTasks => taskbarItems?.SourceCollection as System.Collections.IList; public static DependencyProperty ButtonWidthProperty = DependencyProperty.Register(nameof(ButtonWidth), typeof(double), typeof(TaskList), new PropertyMetadata(new double())); @@ -64,9 +67,12 @@ public Taskbar Host set { SetValue(HostProperty, value); } } + public TaskDropHandler DropHandler { get; set; } + public TaskList() { InitializeComponent(); + DropHandler = new TaskDropHandler(this); } private void SetStyles() @@ -96,16 +102,16 @@ private void TaskList_OnLoaded(object sender, RoutedEventArgs e) private void SetTasksCollection() { - if (!isLoaded && Tasks != null && Host != null) + if (!isLoaded && Tasks?.GroupedWindows != null) { taskbarItems = Tasks.CreateGroupedWindowsCollection(); if (taskbarItems != null) { taskbarItems.CollectionChanged += GroupedWindows_CollectionChanged; taskbarItems.Filter = Tasks_Filter; - } - TasksList.ItemsSource = taskbarItems; + UpdateTasksListItemsSource(); + } Settings.Instance.PropertyChanged += Settings_PropertyChanged; Host.hotkeyManager.TaskbarHotkeyPressed += TaskList_TaskbarHotkeyPressed; @@ -114,6 +120,63 @@ private void SetTasksCollection() } } + private void UpdateTasksListItemsSource() + { + if (taskbarItems == null) return; + + if (Settings.Instance.GroupTaskbarButtons) + { + if (taskbarItems is System.ComponentModel.ICollectionViewLiveShaping taskbarItemsView) + { + taskbarItemsView.IsLiveGrouping = true; + if (!taskbarItemsView.LiveGroupingProperties.Contains("Category")) + { + taskbarItemsView.LiveGroupingProperties.Add("Category"); + } + } + + bool hasCategoryGroup = false; + foreach (var groupDesc in taskbarItems.GroupDescriptions) + { + if (groupDesc is System.Windows.Data.PropertyGroupDescription propDesc && propDesc.PropertyName == "Category") + { + hasCategoryGroup = true; + break; + } + } + if (!hasCategoryGroup) + { + taskbarItems.GroupDescriptions.Add(new System.Windows.Data.PropertyGroupDescription("Category")); + } + + TasksList.ItemsSource = taskbarItems.Groups; + } + else + { + if (taskbarItems is System.ComponentModel.ICollectionViewLiveShaping taskbarItemsView) + { + taskbarItemsView.IsLiveGrouping = false; + taskbarItemsView.LiveGroupingProperties.Remove("Category"); + } + + System.Windows.Data.PropertyGroupDescription categoryGroupDesc = null; + foreach (var groupDesc in taskbarItems.GroupDescriptions) + { + if (groupDesc is System.Windows.Data.PropertyGroupDescription propDesc && propDesc.PropertyName == "Category") + { + categoryGroupDesc = propDesc; + break; + } + } + if (categoryGroupDesc != null) + { + taskbarItems.GroupDescriptions.Remove(categoryGroupDesc); + } + + TasksList.ItemsSource = taskbarItems; + } + } + private static void TasksChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e) { if (sender is TaskList taskList && e.OldValue == null && e.NewValue != null) @@ -124,7 +187,11 @@ private static void TasksChangedCallback(DependencyObject sender, DependencyProp private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Settings.MultiMonMode)) + if (e.PropertyName == nameof(Settings.GroupTaskbarButtons)) + { + UpdateTasksListItemsSource(); + } + else if (e.PropertyName == nameof(Settings.MultiMonMode)) { taskbarItems?.Refresh(); } diff --git a/RetroBar/Controls/TaskThumbnail.xaml.cs b/RetroBar/Controls/TaskThumbnail.xaml.cs index f5835334..a608e077 100644 --- a/RetroBar/Controls/TaskThumbnail.xaml.cs +++ b/RetroBar/Controls/TaskThumbnail.xaml.cs @@ -48,7 +48,15 @@ public IntPtr Handle private IntPtr _thumbHandle; - public static DependencyProperty SourceWindowHandleProperty = DependencyProperty.Register(nameof(SourceWindowHandle), typeof(IntPtr), typeof(TaskThumbnail), new PropertyMetadata(new IntPtr())); + public static DependencyProperty SourceWindowHandleProperty = DependencyProperty.Register(nameof(SourceWindowHandle), typeof(IntPtr), typeof(TaskThumbnail), new PropertyMetadata(new IntPtr(), OnSourceWindowHandleChanged)); + + private static void OnSourceWindowHandleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TaskThumbnail thumbnail) + { + thumbnail.RegisterThumbnail(); + } + } public IntPtr SourceWindowHandle { @@ -85,7 +93,11 @@ public NativeMethods.Rect Rect if (this == null) return new NativeMethods.Rect(0, 0, 0, 0); - var generalTransform = TransformToAncestor((System.Windows.Media.Visual)Parent); + var source = PresentationSource.FromVisual(this); + if (source == null || source.RootVisual == null) + return new NativeMethods.Rect(0, 0, 0, 0); + + var generalTransform = TransformToAncestor(source.RootVisual); var leftTopPoint = generalTransform.Transform(new Point(0, 0)); return new NativeMethods.Rect( (int)(leftTopPoint.X * DpiScale), @@ -190,17 +202,35 @@ private void UserControl_Unloaded(object sender, RoutedEventArgs e) } } - private void UserControl_Loaded(object sender, RoutedEventArgs e) + private void RegisterThumbnail() { - DpiScale = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice.M11; + if (_thumbHandle != IntPtr.Zero) + { + NativeMethods.DwmUnregisterThumbnail(_thumbHandle); + _thumbHandle = IntPtr.Zero; + } if (NativeMethods.DwmIsCompositionEnabled() && SourceWindowHandle != IntPtr.Zero && Handle != IntPtr.Zero && NativeMethods.DwmRegisterThumbnail(Handle, SourceWindowHandle, out _thumbHandle) == 0) { Refresh(); // once loaded, we need to refresh the thumbnail... - _renderingHandler = (s, a) => Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(Refresh)); - CompositionTarget.Rendering += _renderingHandler; + if (_renderingHandler == null) + { + _renderingHandler = (s, a) => Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(Refresh)); + CompositionTarget.Rendering += _renderingHandler; + } } + } + + private void UserControl_Loaded(object sender, RoutedEventArgs e) + { + var source = PresentationSource.FromVisual(this); + if (source?.CompositionTarget != null) + { + DpiScale = source.CompositionTarget.TransformToDevice.M11; + } + + RegisterThumbnail(); _toolTipTimer.Start(); } diff --git a/RetroBar/Converters/TaskGroupVisibilityConverter.cs b/RetroBar/Converters/TaskGroupVisibilityConverter.cs new file mode 100644 index 00000000..57d8684d --- /dev/null +++ b/RetroBar/Converters/TaskGroupVisibilityConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace RetroBar.Converters +{ + public class TaskGroupVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + int count = 0; + if (value is int i) + { + count = i; + } + else if (value is ICollection coll) + { + count = coll.Count; + } + + return count > 1 ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/RetroBar/Converters/TaskLabelConverter.cs b/RetroBar/Converters/TaskLabelConverter.cs index ba176412..50331458 100644 --- a/RetroBar/Converters/TaskLabelConverter.cs +++ b/RetroBar/Converters/TaskLabelConverter.cs @@ -10,27 +10,63 @@ public class TaskLabelConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (!(values[0] is string title && - values[1] is int progressValue && - values[2] is NativeMethods.TBPFLAG progressState)) + if (values == null || values.Length == 0 || !(values[0] is string title)) { return Binding.DoNothing; } + string winFileDescription = values.Length > 1 ? values[1] as string : null; + string winFileName = values.Length > 2 ? values[2] as string : null; + int progressValue = values.Length > 3 && values[3] is int pv ? pv : 0; + NativeMethods.TBPFLAG progressState = values.Length > 4 && values[4] is NativeMethods.TBPFLAG ps ? ps : NativeMethods.TBPFLAG.TBPF_NOPROGRESS; + + int taskCount = 0; + if (values.Length > 5) + { + if (values[5] is int i) + { + taskCount = i; + } + else if (values[5] is System.Collections.ICollection coll) + { + taskCount = coll.Count; + } + } + + string displayTitle = title; + if (taskCount > 1) + { + if (!string.IsNullOrWhiteSpace(winFileDescription)) + { + displayTitle = winFileDescription; + } + else if (!string.IsNullOrWhiteSpace(winFileName)) + { + try + { + displayTitle = System.IO.Path.GetFileNameWithoutExtension(winFileName); + } + catch + { + displayTitle = title; + } + } + } + if (progressState == NativeMethods.TBPFLAG.TBPF_NOPROGRESS || progressState == NativeMethods.TBPFLAG.TBPF_INDETERMINATE || progressValue < 0) { - return title; + return displayTitle; } - if (title.Contains("%")) + if (displayTitle.Contains("%")) { // Window title may already contain progress percentage - return title; + return displayTitle; } - return $"[{Math.Floor(progressValue / 65534.0 * 100)}%] {title}"; + return $"[{Math.Floor(progressValue / 65534.0 * 100)}%] {displayTitle}"; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) diff --git a/RetroBar/Converters/TaskThumbnailItemsConverter.cs b/RetroBar/Converters/TaskThumbnailItemsConverter.cs new file mode 100644 index 00000000..46989a00 --- /dev/null +++ b/RetroBar/Converters/TaskThumbnailItemsConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; +using ManagedShell.WindowsTasks; + +namespace RetroBar.Converters +{ + public class TaskThumbnailItemsConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values != null && values.Length > 1 && values[1] is IEnumerable tasks) + { + var list = tasks.OfType().ToList(); + if (list.Count > 0) + { + return list; + } + } + + if (values != null && values.Length > 0 && values[0] is ApplicationWindow win) + { + return new List { win }; + } + + return null; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/RetroBar/Converters/TaskToolTipConverter.cs b/RetroBar/Converters/TaskToolTipConverter.cs new file mode 100644 index 00000000..8fc08ea7 --- /dev/null +++ b/RetroBar/Converters/TaskToolTipConverter.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Windows.Data; + +namespace RetroBar.Converters +{ + public class TaskToolTipConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null || values.Length == 0 || !(values[0] is string title)) + { + return string.Empty; + } + + string winFileDescription = values.Length > 1 ? values[1] as string : null; + string winFileName = values.Length > 2 ? values[2] as string : null; + + int taskCount = 0; + if (values.Length > 3 && values[3] is ICollection coll) + { + taskCount = coll.Count; + } + + if (taskCount > 1) + { + string programName = title; + if (!string.IsNullOrWhiteSpace(winFileDescription)) + { + programName = winFileDescription; + } + else if (!string.IsNullOrWhiteSpace(winFileName)) + { + try + { + programName = System.IO.Path.GetFileNameWithoutExtension(winFileName); + } + catch + { + programName = title; + } + } + + return $"({taskCount}) {programName}"; + } + + return title; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/RetroBar/Converters/ToolTipPlacementConverter.cs b/RetroBar/Converters/ToolTipPlacementConverter.cs index c50618a3..24856a6f 100644 --- a/RetroBar/Converters/ToolTipPlacementConverter.cs +++ b/RetroBar/Converters/ToolTipPlacementConverter.cs @@ -19,13 +19,9 @@ public object Convert(object[] value, Type targetType, object parameter, System. { return PlacementMode.Left; } - else if (Settings.Instance.Edge == AppBarEdge.Top) - { - return PlacementMode.Bottom; - } else { - return PlacementMode.Top; + return PlacementMode.Custom; } } diff --git a/RetroBar/Languages/English.xaml b/RetroBar/Languages/English.xaml index a7a56ffe..b1c8eb12 100644 --- a/RetroBar/Languages/English.xaml +++ b/RetroBar/Languages/English.xaml @@ -79,6 +79,7 @@ Check for u_pdates Show E_xit option in right-click menu Show End _task option in right-click menu + _Group similar taskbar buttons Slid_e taskbar buttons S_how seconds in the clock Open custom themes folder @@ -145,6 +146,9 @@ _Close _End task + _Minimize Group + _Close Group + Show hidden icons Hide diff --git "a/RetroBar/Languages/espa\303\261ol.xaml" "b/RetroBar/Languages/espa\303\261ol.xaml" index 2103891c..a5930d91 100644 --- "a/RetroBar/Languages/espa\303\261ol.xaml" +++ "b/RetroBar/Languages/espa\303\261ol.xaml" @@ -79,6 +79,7 @@ Comprobar _actualizaciones Mostrar elemento "_Salir de RetroBar" en el menú contextual Mostrar elemento "_Finalizar tarea" en el menú contextual + _Agrupar los botones similares de la barra de tareas Deslizar _botones de la barra de tareas Mostrar _segundos en el reloj Abrir carpeta de temas personalizados @@ -145,6 +146,9 @@ _Cerrar _Finalizar tarea + _Minimizar grupo + C_errar grupo + Mostrar iconos ocultos Ocultar diff --git a/RetroBar/PropertiesWindow.xaml b/RetroBar/PropertiesWindow.xaml index a4a54095..dd47d230 100644 --- a/RetroBar/PropertiesWindow.xaml +++ b/RetroBar/PropertiesWindow.xaml @@ -267,6 +267,10 @@ IsChecked="{Binding Source={x:Static Settings:Settings.Instance}, Path=ShowTaskThumbnails, UpdateSourceTrigger=PropertyChanged}">