From 0592bbe0daccee3cefd6f8262c5c532eb8d2dec0 Mon Sep 17 00:00:00 2001
From: wjingshan <22065084+wjingshan@users.noreply.github.com>
Date: Sat, 12 Sep 2026 12:30:53 +0800
Subject: [PATCH] feat(widget): add a custom left margin for the taskbar widget
The widget position was always derived from the position setting plus the
automatic padding (which snaps it next to the native Widgets button), so there
was no way to place it at an exact distance from the start of the taskbar.
- new "TaskbarWidgetUseCustomLeftMargin" toggle and "TaskbarWidgetLeftMargin"
value (0-2000 px, default 20) with a numeric text box
- with the position set to "near start" the widget's left edge (top edge on a
vertical taskbar) is placed at exactly that distance, ignoring the automatic
padding; the existing manual padding still adds an offset on top
- the visualizer is moved to the right of the widget when there is not enough
room on the left, so a small margin can no longer push it off the taskbar
Disabled by default, so nothing changes unless the toggle is turned on.
---
FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml | 41 ++++++++++-----
.../Localization/Dictionary-en-US.xaml | 2 +
FluentFlyoutWPF/ViewModels/UserSettings.cs | 52 +++++++++++++++++++
FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs | 20 +++++++
4 files changed, 103 insertions(+), 12 deletions(-)
diff --git a/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml b/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml
index d7dc16b96..3e97248ae 100644
--- a/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml
+++ b/FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml
@@ -35,6 +35,7 @@
+
@@ -116,7 +117,23 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -127,7 +144,7 @@
-
+
@@ -181,7 +198,7 @@
-
+
@@ -190,7 +207,7 @@
-
+
@@ -199,7 +216,7 @@
-
+
@@ -210,7 +227,7 @@
-
+
@@ -219,7 +236,7 @@
-
+
@@ -228,7 +245,7 @@
-
+
@@ -241,7 +258,7 @@
-
+
@@ -258,7 +275,7 @@
-
+
@@ -295,7 +312,7 @@
-
+
@@ -305,7 +322,7 @@
-
+
diff --git a/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml b/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
index 36b0a29b4..4cfa08cf0 100644
--- a/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
+++ b/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
@@ -67,6 +67,8 @@ hides repeat, shuffle and player info
Add background blur effect to the taskbar widget
Hide Completely When No Media Is Present
Hides the entire widget when no media is available, instead of displaying a media icon
+ Left Margin
+ Distance from the left edge of the taskbar (top edge on a vertical taskbar). Overrides the position and automatic padding settings; the unit is device independent pixels
Fixed Widget Width
Keep the widget at a fixed width instead of resizing to fit the title and artist text
Autohide Widget
diff --git a/FluentFlyoutWPF/ViewModels/UserSettings.cs b/FluentFlyoutWPF/ViewModels/UserSettings.cs
index 8c25a35a1..ee68db0df 100644
--- a/FluentFlyoutWPF/ViewModels/UserSettings.cs
+++ b/FluentFlyoutWPF/ViewModels/UserSettings.cs
@@ -6,6 +6,7 @@
using FluentFlyout.Classes.Settings;
using FluentFlyout.Classes.Utils;
using FluentFlyout.Controls;
+using FluentFlyout.Windows;
using FluentFlyoutWPF.Classes;
using FluentFlyoutWPF.Models;
using FluentFlyoutWPF.Windows;
@@ -434,6 +435,43 @@ public string TaskbarWidgetManualPaddingText
[ObservableProperty]
public partial bool TaskbarWidgetFixedWidth { get; set; }
+ ///
+ /// Gets or sets whether the widget is positioned by instead of the
+ /// widget position / automatic padding settings (only used when the position is "near start").
+ ///
+ [ObservableProperty]
+ public partial bool TaskbarWidgetUseCustomLeftMargin { get; set; }
+
+ ///
+ /// Distance of the taskbar widget from the start of the taskbar (left edge, or top edge on a vertical
+ /// taskbar) in device independent pixels, used when is enabled.
+ ///
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(TaskbarWidgetLeftMarginText))]
+ public partial int TaskbarWidgetLeftMargin { get; set; }
+
+ ///
+ /// Text representation of for the settings text box.
+ ///
+ [XmlIgnore]
+ public string TaskbarWidgetLeftMarginText
+ {
+ get => TaskbarWidgetLeftMargin.ToString();
+ set
+ {
+ if (int.TryParse(value, out var result))
+ {
+ TaskbarWidgetLeftMargin = Math.Clamp(result, TaskbarWindow.MinCustomLeftMargin, TaskbarWindow.MaxCustomLeftMargin);
+ }
+ else
+ {
+ TaskbarWidgetLeftMargin = 20;
+ }
+
+ OnPropertyChanged();
+ }
+ }
+
///
/// Gets or sets a value indicating whether the pause icon overlay should be completely hidden from view.
///
@@ -752,6 +790,8 @@ public UserSettings()
TaskbarWidgetBackgroundBlur = false;
TaskbarWidgetHideCompletely = false;
TaskbarWidgetFixedWidth = false;
+ TaskbarWidgetUseCustomLeftMargin = false;
+ TaskbarWidgetLeftMargin = 20;
TaskbarWidgetShowPauseOverlay = true;
TaskbarWidgetControlsEnabled = false;
TaskbarWidgetControlsPosition = 1;
@@ -926,6 +966,18 @@ partial void OnTaskbarWidgetFixedWidthChanged(bool oldValue, bool newValue)
UpdateTaskbar();
}
+ partial void OnTaskbarWidgetUseCustomLeftMarginChanged(bool oldValue, bool newValue)
+ {
+ if (oldValue == newValue || _initializing) return;
+ UpdateTaskbar();
+ }
+
+ partial void OnTaskbarWidgetLeftMarginChanged(int oldValue, int newValue)
+ {
+ if (oldValue == newValue || _initializing) return;
+ UpdateTaskbar();
+ }
+
partial void OnTaskbarWidgetShowPauseOverlayChanged(bool oldValue, bool newValue)
{
if (oldValue == newValue || _initializing) return;
diff --git a/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs b/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs
index 1d2e4f279..357d92d20 100644
--- a/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs
+++ b/FluentFlyoutWPF/Windows/TaskbarWindow.xaml.cs
@@ -27,6 +27,10 @@ public partial class TaskbarWindow : Window
private const double SmallTaskbarDetectionThreshold = 40;
+ // limits for TaskbarWidgetLeftMargin (device independent pixels)
+ public const int MinCustomLeftMargin = 0;
+ public const int MaxCustomLeftMargin = 2000;
+
private readonly DispatcherTimer _timer;
private readonly int _nativeWidgetsPadding = 216;
private readonly double _scale = 0.9;
@@ -486,6 +490,15 @@ private Rect PositionWidget(IntPtr taskbarHandle, RECT taskbarRect, double dpiSc
switch (SettingsManager.Current.TaskbarWidgetPosition)
{
case 0: // near start (left for horizontal, top for vertical)
+ // custom left margin: place the widget's left edge (top edge on a vertical taskbar)
+ // at exactly the configured distance from the start of the taskbar
+ if (SettingsManager.Current.TaskbarWidgetUseCustomLeftMargin)
+ {
+ primaryPos = (int)(Math.Clamp(SettingsManager.Current.TaskbarWidgetLeftMargin,
+ MinCustomLeftMargin, MaxCustomLeftMargin) * dpiScale);
+ break;
+ }
+
primaryPos = 20;
if (SettingsManager.Current.TaskbarVisualizerEnabled && SettingsManager.Current.TaskbarVisualizerPosition == 0)
@@ -684,6 +697,13 @@ private Rect PositionVisualizer(IntPtr taskbarHandle, RECT taskbarRect, double d
break;
}
+ // with a small custom left margin the visualizer would be pushed off the start of the taskbar,
+ // so keep it on screen by placing it after the widget instead
+ if (primaryPos < 0)
+ {
+ primaryPos = (int)(widgetPrimaryStart * dpiScale) + (int)(Widget.Width * dpiScale);
+ }
+
// Set visualizer position within canvas
// primaryPos → left (horizontal) or top (vertical); crossPos → top (horizontal) or left (vertical)
Canvas.SetLeft(TaskbarVisualizer, (isVertical ? crossPos : primaryPos) / dpiScale);