Skip to content

Commit 1667c70

Browse files
committed
feat: taskbar mode
1 parent 160398f commit 1667c70

File tree

7 files changed

+121
-47
lines changed

7 files changed

+121
-47
lines changed

BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/WindowSettingsControl.xaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,21 @@
8686
</dev:SettingsExpander.Items>
8787
</dev:SettingsExpander>
8888

89-
<dev:SettingsCard x:Uid="SettingsPagePinToTaskbar" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C4;}">
89+
<dev:SettingsExpander
90+
x:Uid="SettingsPagePinToTaskbar"
91+
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
92+
Glyph=&#xE7C4;}"
93+
IsExpanded="{x:Bind LyricsWindowStatus.IsPinToTaskbar, Mode=OneWay}">
9094
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsPinToTaskbar, Mode=TwoWay}" />
91-
</dev:SettingsCard>
95+
<dev:SettingsExpander.Items>
96+
<dev:SettingsCard x:Uid="SettingsPageTaskbarPlacement">
97+
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.TaskbarPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
98+
<ComboBoxItem x:Uid="SettingsPageLeft" />
99+
<ComboBoxItem x:Uid="SettingsPageRight" />
100+
</ComboBox>
101+
</dev:SettingsCard>
102+
</dev:SettingsExpander.Items>
103+
</dev:SettingsExpander>
92104

93105
<dev:SettingsExpander
94106
x:Uid="SettingsPageAOT"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace BetterLyrics.WinUI3.Enums
6+
{
7+
public enum TaskbarPlacement
8+
{
9+
Left,
10+
Right,
11+
}
12+
}

BetterLyrics.WinUI3/BetterLyrics.WinUI3/Hooks/TaskbarHook.cs

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using BetterLyrics.WinUI3.Events;
1+
using BetterLyrics.WinUI3.Enums;
2+
using BetterLyrics.WinUI3.Events;
23
using BetterLyrics.WinUI3.Extensions;
34
using FlaUI.Core;
45
using FlaUI.Core.AutomationElements;
@@ -21,21 +22,31 @@ public class TaskbarHook : IDisposable
2122
private StructureChangedEventHandlerBase? _structureHandler;
2223
private PropertyChangedEventHandlerBase? _propertyHandler;
2324

25+
private TaskbarPlacement _currentPlacement;
26+
2427
private readonly DispatcherQueue _dispatcherQueue;
2528
private readonly Action<TaskbarFreeBoundsChangedEventArgs> _onLayoutChanged;
2629
private Timer? _debounceTimer;
2730
private const int DebounceDelay = 150;
2831
private bool _isDisposed;
2932

30-
public TaskbarHook(Action<TaskbarFreeBoundsChangedEventArgs> onLayoutChanged)
33+
public TaskbarHook(TaskbarPlacement placement, Action<TaskbarFreeBoundsChangedEventArgs> onLayoutChanged)
3134
{
3235
_automation = new UIA3Automation();
3336
_onLayoutChanged = onLayoutChanged;
3437
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
3538

39+
_currentPlacement = placement;
40+
3641
StartHook();
3742
}
3843

44+
public void UpdatePlacement(TaskbarPlacement newPlacement)
45+
{
46+
_currentPlacement = newPlacement;
47+
RequestUpdate(); // 立即刷新位置
48+
}
49+
3950
private void StartHook()
4051
{
4152
try
@@ -71,7 +82,7 @@ private void RequestUpdate()
7182
_debounceTimer?.Dispose();
7283
_debounceTimer = new Timer(_ =>
7384
{
74-
Rectangle voidRect = CalculateVoidRect();
85+
Rectangle voidRect = CalculateVoidRect(_currentPlacement);
7586
_dispatcherQueue.TryEnqueue(() =>
7687
{
7788
if (!_isDisposed && voidRect != Rectangle.Empty)
@@ -82,13 +93,12 @@ private void RequestUpdate()
8293
}, null, DebounceDelay, Timeout.Infinite);
8394
}
8495

85-
private Rectangle CalculateVoidRect()
96+
private Rectangle CalculateVoidRect(TaskbarPlacement placement)
8697
{
8798
try
8899
{
89100
if (_taskbar == null) return Rectangle.Empty;
90101

91-
// 重新获取任务栏边界
92102
try { var _ = _taskbar.BoundingRectangle; }
93103
catch
94104
{
@@ -99,68 +109,86 @@ private Rectangle CalculateVoidRect()
99109

100110
Rectangle taskbarRect = _taskbar.BoundingRectangle;
101111

102-
// 确定右边界
103-
int gapRight = taskbarRect.Right;
104-
112+
// 绝对右边界:托盘
113+
int barrierRight = taskbarRect.Right;
105114
var tray = _taskbar.FindFirstDescendant(cf => cf.ByAutomationId("SystemTrayIcon")); // Win11
106115
if (tray == null) tray = _taskbar.FindFirstDescendant(cf => cf.ByClassName("TrayNotifyWnd")); // Win10
116+
if (tray != null) barrierRight = tray.BoundingRectangle.Left;
107117

108-
if (tray != null)
118+
// 绝对左边界:任务栏左边缘 或 小组件(Win11)
119+
int barrierLeft = taskbarRect.Left;
120+
var widgets = _taskbar.FindFirstDescendant(cf => cf.ByAutomationId("WidgetsButton"));
121+
122+
// 只有当小组件确实在最左侧时 (Win11默认),它才构成左边界
123+
// 如果用户把任务栏设为靠左对齐,小组件会在开始按钮右边,这时候不把它当做左边界
124+
if (widgets != null && widgets.BoundingRectangle.Left < taskbarRect.Left + 100)
109125
{
110-
gapRight = tray.BoundingRectangle.Left;
126+
barrierLeft = (int)widgets.BoundingRectangle.Right;
111127
}
112128

113-
int gapLeft = taskbarRect.Left;
114129

115-
// 系统按钮
130+
// 寻找 中间内容区域 (Start + Search + Apps) 的 左右极值
131+
int contentMinLeft = barrierRight;
132+
int contentMaxRight = barrierLeft;
133+
134+
// 定义所有中间元素
116135
string[] systemButtonIds = new[] {
117-
"StartButton",
118-
"SearchButton",
119-
"TaskViewButton",
120-
"WidgetsButton",
121-
"ChatButton"
136+
"StartButton", "SearchButton", "TaskViewButton", "ChatButton"
122137
};
123138

139+
// 系统按钮
124140
foreach (var id in systemButtonIds)
125141
{
126142
var btn = _taskbar.FindFirstDescendant(cf => cf.ByAutomationId(id));
127143
if (btn != null)
128144
{
129145
var rect = btn.BoundingRectangle;
130-
// 只有当按钮在托盘左侧时,才计算它
131-
if (rect.Right < gapRight && rect.Right > gapLeft)
132-
{
133-
gapLeft = (int)rect.Right;
134-
}
146+
// 排除不可见的
147+
if (rect.Width <= 0) continue;
148+
149+
// 更新极值
150+
if (rect.Left < contentMinLeft) contentMinLeft = (int)rect.Left;
151+
if (rect.Right > contentMaxRight) contentMaxRight = (int)rect.Right;
135152
}
136153
}
137154

138-
// 遍历图标
155+
// App 图标
139156
var appIcons = _taskbar.FindAllDescendants(cf => cf.ByClassName("Taskbar.TaskListButtonAutomationPeer"));
140-
141157
foreach (var icon in appIcons)
142158
{
143159
var rect = icon.BoundingRectangle;
160+
if (rect.Width <= 0) continue;
144161

145-
// 过滤无效的图标 (宽高为 0 的隐藏图标)
146-
if (rect.Width <= 0 || rect.Height <= 0) continue;
147-
148-
// 如果这个图标确实在托盘左边,更新 gapLeft
149-
if (rect.Right < gapRight && rect.Right > gapLeft)
150-
{
151-
gapLeft = (int)rect.Right;
152-
}
162+
if (rect.Left < contentMinLeft) contentMinLeft = (int)rect.Left;
163+
if (rect.Right > contentMaxRight) contentMaxRight = (int)rect.Right;
153164
}
154165

155-
// 增加一点点间距
156-
gapLeft += 10;
157-
gapRight -= 10;
166+
// 如果完全没找到内容,重置为中间
167+
if (contentMinLeft == barrierRight) contentMinLeft = taskbarRect.Left;
168+
if (contentMaxRight == barrierLeft) contentMaxRight = taskbarRect.Left;
169+
170+
int finalLeft, finalRight;
171+
int padding = 10;
172+
173+
if (placement == TaskbarPlacement.Left)
174+
{
175+
// 【小组件】... [空隙] ...【开始按钮】
176+
// 如果是 Win10 或 Win11左对齐,contentMinLeft 几乎等于 barrierLeft,空隙为0
177+
finalLeft = barrierLeft + padding;
178+
finalRight = contentMinLeft - padding;
179+
}
180+
else // Right
181+
{
182+
// 【最后一个图标】... [空隙] ...【托盘】
183+
finalLeft = contentMaxRight + padding;
184+
finalRight = barrierRight - padding;
185+
}
158186

159-
int width = gapRight - gapLeft;
187+
int width = finalRight - finalLeft;
160188

161189
if (width < 20) return Rectangle.Empty;
162190

163-
return new Rectangle(gapLeft, taskbarRect.Top, width, taskbarRect.Height);
191+
return new Rectangle(finalLeft, taskbarRect.Top, width, taskbarRect.Height);
164192
}
165193
catch (Exception ex)
166194
{

BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsWindowStatus.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ public partial class LyricsWindowStatus : ObservableRecipient, ICloneable
2222
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysOnTopPolling { get; set; } = false;
2323
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsShownInSwitchers { get; set; } = true;
2424
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLocked { get; set; } = false;
25+
2526
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPinToTaskbar { get; set; } = false;
27+
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TaskbarPlacement TaskbarPlacement { get; set; } = TaskbarPlacement.Right;
28+
2629
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMaximized { get; set; } = false;
2730
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFullscreen { get; set; } = false;
2831
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsLayoutOrientation LyricsLayoutOrientation { get; set; } = LyricsLayoutOrientation.Horizontal;
@@ -138,7 +141,10 @@ public object Clone()
138141
IsAlwaysOnTopPolling = this.IsAlwaysOnTopPolling,
139142
IsShownInSwitchers = this.IsShownInSwitchers,
140143
IsLocked = this.IsLocked,
144+
141145
IsPinToTaskbar = this.IsPinToTaskbar,
146+
TaskbarPlacement = this.TaskbarPlacement,
147+
142148
IsMaximized = this.IsMaximized,
143149
IsFullscreen = this.IsFullscreen,
144150

BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,9 @@
15121512
<data name="SettingsPageTaskbarMode.Text" xml:space="preserve">
15131513
<value>Taskbar mode</value>
15141514
</data>
1515+
<data name="SettingsPageTaskbarPlacement.Header" xml:space="preserve">
1516+
<value />
1517+
</data>
15151518
<data name="SettingsPageTC.Content" xml:space="preserve">
15161519
<value>繁體中文</value>
15171520
</data>

BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,9 @@
15121512
<data name="SettingsPageTaskbarMode.Text" xml:space="preserve">
15131513
<value>任务栏模式</value>
15141514
</data>
1515+
<data name="SettingsPageTaskbarPlacement.Header" xml:space="preserve">
1516+
<value>任务栏固定位置</value>
1517+
</data>
15151518
<data name="SettingsPageTC.Content" xml:space="preserve">
15161519
<value>繁體中文</value>
15171520
</data>

BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/NowPlayingWindow.xaml.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public sealed partial class NowPlayingWindow : Window,
3535
IRecipient<PropertyChangedMessage<ElementTheme>>,
3636
IRecipient<PropertyChangedMessage<BitmapImage?>>,
3737
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
38-
IRecipient<PropertyChangedMessage<Color>>
38+
IRecipient<PropertyChangedMessage<Color>>,
39+
IRecipient<PropertyChangedMessage<TaskbarPlacement>>
3940
{
4041
private ForegroundWindowHook? _fgWindowWatcher = null;
4142
private OverlayInputHelper? _overlayInputHelper;
@@ -56,11 +57,12 @@ public NowPlayingWindow(LyricsWindowStatus status)
5657
this.InitializeComponent();
5758

5859
_fgWindowWatcherTimer = DispatcherQueue.CreateTimer();
59-
_taskbarHook = new TaskbarHook(OnTaskbarFreeBoundsChanged);
6060

6161
LyricsWindowStatus = status;
6262
NowPlayingPage.LyricsWindowStatus = LyricsWindowStatus;
6363

64+
_taskbarHook = new TaskbarHook(LyricsWindowStatus.TaskbarPlacement, OnTaskbarFreeBoundsChanged);
65+
6466
this.Init("LyricsPageTitle", TitleBarHeightOption.Collapsed, BackdropType.Transparent);
6567

6668
AppWindow.Changed += AppWindow_Changed;
@@ -196,14 +198,12 @@ private void OnIsLockedChanged()
196198

197199
private void OnIsPinToTaskbarChanged()
198200
{
201+
_taskbarHook?.Dispose();
202+
_taskbarHook = null;
203+
199204
if (LyricsWindowStatus.IsPinToTaskbar)
200205
{
201-
_taskbarHook = new(OnTaskbarFreeBoundsChanged);
202-
}
203-
else
204-
{
205-
_taskbarHook?.Dispose();
206-
_taskbarHook = null;
206+
_taskbarHook = new(LyricsWindowStatus.TaskbarPlacement, OnTaskbarFreeBoundsChanged);
207207
}
208208
}
209209

@@ -614,5 +614,15 @@ public void Receive(PropertyChangedMessage<Color> message)
614614
}
615615
}
616616

617+
public void Receive(PropertyChangedMessage<TaskbarPlacement> message)
618+
{
619+
if (message.Sender == LyricsWindowStatus)
620+
{
621+
if (message.PropertyName == nameof(LyricsWindowStatus.TaskbarPlacement))
622+
{
623+
_taskbarHook?.UpdatePlacement(LyricsWindowStatus.TaskbarPlacement);
624+
}
625+
}
626+
}
617627
}
618628
}

0 commit comments

Comments
 (0)