Skip to content
Open
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
24 changes: 24 additions & 0 deletions FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,30 @@
</controls:ToggleSwitch.Style>
</controls:ToggleSwitch>
</ui:CardControl>
<ui:CardControl Icon="{ui:SymbolIcon ArrowFit20}" Margin="0,0,0,3">
<ui:CardControl.Header>
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource TaskbarWidgetPreventStartOverlapTitle}" FontSize="14" FontWeight="Regular" VerticalAlignment="Center" />
<TextBlock Text="{DynamicResource TaskbarWidgetPreventStartOverlapDescription}" FontSize="12" Opacity="0.5" />
</StackPanel>
</ui:CardControl.Header>
<controls:ToggleSwitch IsChecked="{Binding TaskbarWidgetPreventStartOverlap, Mode=TwoWay}">
<controls:ToggleSwitch.Style>
<Style TargetType="controls:ToggleSwitch" BasedOn="{StaticResource {x:Type controls:ToggleSwitch}}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsPremiumUnlocked}" Value="True" />
<Condition Binding="{Binding TaskbarWidgetPosition}" Value="0" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</controls:ToggleSwitch.Style>
</controls:ToggleSwitch>
</ui:CardControl>
<ui:CardControl Icon="{ui:SymbolIcon ArrowFit20}" Margin="0,0,0,0">
<ui:CardControl.Header>
<StackPanel Orientation="Vertical">
Expand Down
2 changes: 2 additions & 0 deletions FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ hides repeat, shuffle and player info</System:String>
<System:String x:Key="TaskbarWidgetPaddingSettingsDescription">Adjust the padding around the widget to fit your taskbar style</System:String>
<System:String x:Key="TaskbarWidgetPaddingTitle">Automatic Padding for Windows Widgets button</System:String>
<System:String x:Key="TaskbarWidgetPaddingDescription">Turn this feature on if you have Windows Widgets enabled</System:String>
<System:String x:Key="TaskbarWidgetPreventStartOverlapTitle">Prevent overlap with Start button</System:String>
<System:String x:Key="TaskbarWidgetPreventStartOverlapDescription">For left alignment, shrink widget width if space before Start is limited</System:String>
<System:String x:Key="TaskbarWidgetManualPaddingTitle">Custom Padding</System:String>
<System:String x:Key="TaskbarWidgetManualPaddingDescription">Add extra padding (default: 0px)</System:String>
<System:String x:Key="TaskbarWidgetBackgroundBlurTitle">Background Blur</System:String>
Expand Down
14 changes: 14 additions & 0 deletions FluentFlyoutWPF/ViewModels/UserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,13 @@ public string LockKeysDurationText
[ObservableProperty]
public partial bool TaskbarWidgetPadding { get; set; }

/// <summary>
/// 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.
/// </summary>
[ObservableProperty]
public partial bool TaskbarWidgetPreventStartOverlap { get; set; }

/// <summary>
/// Manual padding value in pixels applied to the taskbar widget
/// </summary>
Expand Down Expand Up @@ -623,6 +630,7 @@ public UserSettings()
TaskbarWidgetSelectedMonitor = 0;
TaskbarWidgetPosition = 0;
TaskbarWidgetPadding = true;
TaskbarWidgetPreventStartOverlap = false;
TaskbarWidgetManualPadding = 0;
TaskbarWidgetBackgroundBlur = false;
TaskbarWidgetHideCompletely = false;
Expand Down Expand Up @@ -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;
Expand Down
63 changes: 56 additions & 7 deletions FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Task> _pendingAutomationTasks = [];

private GlobalSystemMediaTransportControlsSessionPlaybackStatus? _lastPlaybackStatus;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -670,17 +710,17 @@ 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;
var findTask = Task.Run(() =>
{
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))
{
Expand All @@ -698,15 +738,15 @@ 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);
}

var cachedElement = elementCache;
var boundsTask = Task.Run(() => cachedElement.Current.BoundingRectangle);
_pendingAutomationTasks[elementName] = boundsTask;
_pendingAutomationTasks[pendingTaskKey] = boundsTask;

if (!boundsTask.Wait(500))
{
Expand Down Expand Up @@ -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);
}
}