From 4196b385869d1db0ba070c2b7e7bd4918b3d3ba0 Mon Sep 17 00:00:00 2001 From: Dusks Date: Sat, 25 Apr 2026 19:12:15 -0700 Subject: [PATCH] Added taskbar widget overlap prevention feature Co-authored-by: Copilot --- FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml | 24 +++++++ .../Localization/Dictionary-en-US.xaml | 2 + FluentFlyoutWPF/ViewModels/UserSettings.cs | 14 +++++ FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs | 63 ++++++++++++++++--- 4 files changed, 96 insertions(+), 7 deletions(-) diff --git a/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml b/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml index ebed25c8..a8731d2d 100644 --- a/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml +++ b/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml @@ -174,6 +174,30 @@ + + + + + + + + + + + + + diff --git a/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml b/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml index 2a43079a..367f7a00 100644 --- a/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml +++ b/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml @@ -56,6 +56,8 @@ hides repeat, shuffle and player info Adjust the padding around the widget to fit your taskbar style Automatic Padding for Windows Widgets button Turn this feature on if you have Windows Widgets enabled + Prevent overlap with Start button + For left alignment, shrink widget width if space before Start is limited Custom Padding Add extra padding (default: 0px) Background Blur diff --git a/FluentFlyoutWPF/ViewModels/UserSettings.cs b/FluentFlyoutWPF/ViewModels/UserSettings.cs index 27544d98..25db0bfa 100644 --- a/FluentFlyoutWPF/ViewModels/UserSettings.cs +++ b/FluentFlyoutWPF/ViewModels/UserSettings.cs @@ -363,6 +363,13 @@ public string LockKeysDurationText [ObservableProperty] public partial bool TaskbarWidgetPadding { get; set; } + /// + /// Prevent overlap with the Start button by shrinking the taskbar widget width when there is not enough space. + /// Applies only when the widget position is set to left. + /// + [ObservableProperty] + public partial bool TaskbarWidgetPreventStartOverlap { get; set; } + /// /// Manual padding value in pixels applied to the taskbar widget /// @@ -623,6 +630,7 @@ public UserSettings() TaskbarWidgetSelectedMonitor = 0; TaskbarWidgetPosition = 0; TaskbarWidgetPadding = true; + TaskbarWidgetPreventStartOverlap = false; TaskbarWidgetManualPadding = 0; TaskbarWidgetBackgroundBlur = false; TaskbarWidgetHideCompletely = false; @@ -720,6 +728,12 @@ partial void OnTaskbarWidgetManualPaddingChanged(int oldValue, int newValue) UpdateTaskbar(); } + partial void OnTaskbarWidgetPreventStartOverlapChanged(bool oldValue, bool newValue) + { + if (oldValue == newValue || _initializing) return; + UpdateTaskbar(); + } + partial void OnTaskbarWidgetBackgroundBlurChanged(bool oldValue, bool newValue) { if (oldValue == newValue || _initializing) return; diff --git a/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs b/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs index de533297..df3e32e0 100644 --- a/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs +++ b/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs @@ -33,10 +33,13 @@ public partial class TaskbarWindow : Window private AutomationElement? _widgetElement; private AutomationElement? _trayElement; private AutomationElement? _taskbarFrameElement; + private AutomationElement? _startButtonElement; + private AutomationElement? _startButtonByNameElement; // reference to main window for flyout functions private MainWindow? _mainWindow; private int _lastSelectedMonitor = -1; private bool _positionUpdateInProgress; + private bool _startButtonLookupWarningLogged; private readonly Dictionary _pendingAutomationTasks = []; private GlobalSystemMediaTransportControlsSessionPlaybackStatus? _lastPlaybackStatus; @@ -546,10 +549,44 @@ private Rect PositionWidget(IntPtr taskbarHandle, RECT taskbarRect, double dpiSc widgetLeft += SettingsManager.Current.TaskbarWidgetManualPadding; + int finalPhysicalWidth = physicalWidth; + if (SettingsManager.Current.TaskbarWidgetPosition == 0 && SettingsManager.Current.TaskbarWidgetPreventStartOverlap) + { + const int startButtonGap = 2; + (bool foundStartButton, Rect startButtonRect) = GetStartButtonRect(taskbarHandle); + bool hasValidStartBounds = foundStartButton && + startButtonRect.Width > 0 && + startButtonRect.Left > taskbarRect.Left && + startButtonRect.Left < taskbarRect.Right && + startButtonRect.Left < (taskbarRect.Left + taskbarRect.Right) / 2.0; + + if (hasValidStartBounds) + { + _startButtonLookupWarningLogged = false; + + int startLeftInTaskbar = (int)(startButtonRect.Left - taskbarRect.Left); + int noOverlapRightBoundary = startLeftInTaskbar - startButtonGap; + + if (widgetLeft >= noOverlapRightBoundary) + widgetLeft = Math.Max(0, noOverlapRightBoundary - 1); + + int maxWidthToFitGap = noOverlapRightBoundary - widgetLeft; + if (maxWidthToFitGap < finalPhysicalWidth) + { + finalPhysicalWidth = Math.Max(1, maxWidthToFitGap); + } + } + else if (!_startButtonLookupWarningLogged) + { + Logger.Warn("Failed to resolve Start button bounds for taskbar overlap prevention. Using existing widget width."); + _startButtonLookupWarningLogged = true; + } + } + // Set widget position within canvas Canvas.SetLeft(Widget, widgetLeft / dpiScale); Canvas.SetTop(Widget, widgetTop / dpiScale); - Widget.Width = physicalWidth / dpiScale; + Widget.Width = finalPhysicalWidth / dpiScale; Widget.Height = physicalHeight / dpiScale; return new Rect(Canvas.GetLeft(Widget) * dpiScale, Canvas.GetTop(Widget) * dpiScale, Widget.Width * dpiScale, Widget.Height * dpiScale); @@ -656,11 +693,14 @@ public void UpdateUi(string title, string artist, BitmapImage? icon, GlobalSyste }); } - private (bool, Rect) GetTaskbarXamlElementRect(IntPtr taskbarHandle, ref AutomationElement? elementCache, string elementName) + private (bool, Rect) GetTaskbarXamlElementRect(IntPtr taskbarHandle, ref AutomationElement? elementCache, string elementName, AutomationProperty? property = null) { if (taskbarHandle == IntPtr.Zero) return (false, Rect.Empty); + AutomationProperty lookupProperty = property ?? AutomationElement.AutomationIdProperty; + string pendingTaskKey = $"{lookupProperty.ProgrammaticName}:{elementName}"; + try { // reset if monitor changed @@ -670,7 +710,7 @@ public void UpdateUi(string title, string artist, BitmapImage? icon, GlobalSyste // find widget in XAML if (elementCache == null) { - if (_pendingAutomationTasks.TryGetValue(elementName, out var pendingTask) && !pendingTask.IsCompleted) + if (_pendingAutomationTasks.TryGetValue(pendingTaskKey, out var pendingTask) && !pendingTask.IsCompleted) return (false, Rect.Empty); AutomationElement? found = null; @@ -678,9 +718,9 @@ public void UpdateUi(string title, string artist, BitmapImage? icon, GlobalSyste { var root = AutomationElement.FromHandle(taskbarHandle); found = root.FindFirst(TreeScope.Descendants, - new PropertyCondition(AutomationElement.AutomationIdProperty, elementName)); + new PropertyCondition(lookupProperty, elementName)); }); - _pendingAutomationTasks[elementName] = findTask; + _pendingAutomationTasks[pendingTaskKey] = findTask; if (!findTask.Wait(1000)) { @@ -698,7 +738,7 @@ public void UpdateUi(string title, string artist, BitmapImage? icon, GlobalSyste try { - if (_pendingAutomationTasks.TryGetValue(elementName, out var pendingTask) && !pendingTask.IsCompleted) + if (_pendingAutomationTasks.TryGetValue(pendingTaskKey, out var pendingTask) && !pendingTask.IsCompleted) { elementCache = null; return (false, Rect.Empty); @@ -706,7 +746,7 @@ public void UpdateUi(string title, string artist, BitmapImage? icon, GlobalSyste var cachedElement = elementCache; var boundsTask = Task.Run(() => cachedElement.Current.BoundingRectangle); - _pendingAutomationTasks[elementName] = boundsTask; + _pendingAutomationTasks[pendingTaskKey] = boundsTask; if (!boundsTask.Wait(500)) { @@ -773,4 +813,13 @@ public void UpdateUi(string title, string artist, BitmapImage? icon, GlobalSyste { return GetTaskbarXamlElementRect(taskbarHandle, ref _taskbarFrameElement, "TaskbarFrame"); } + + private (bool, Rect) GetStartButtonRect(IntPtr taskbarHandle) + { + (bool foundById, Rect startRect) = GetTaskbarXamlElementRect(taskbarHandle, ref _startButtonElement, "StartButton"); + if (foundById) + return (true, startRect); + + return GetTaskbarXamlElementRect(taskbarHandle, ref _startButtonByNameElement, "Start", AutomationElement.NameProperty); + } } \ No newline at end of file