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