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);