Skip to content

Commit 5638bf1

Browse files
committed
feat: calculate taskbar position and display on top of it
1 parent 059179c commit 5638bf1

File tree

1 file changed

+97
-52
lines changed

1 file changed

+97
-52
lines changed

App/Views/TrayWindow.xaml.cs

Lines changed: 97 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
using Microsoft.UI.Windowing;
1010
using Microsoft.UI.Xaml;
1111
using Microsoft.UI.Xaml.Controls;
12-
using Microsoft.UI.Xaml.Media;
1312
using Microsoft.UI.Xaml.Media.Animation;
1413
using System;
15-
using System.Diagnostics;
14+
using System.Drawing.Printing;
1615
using System.Runtime.InteropServices;
1716
using Windows.Graphics;
1817
using Windows.System;
@@ -34,8 +33,6 @@ public sealed partial class TrayWindow : Window
3433
private int _lastWindowHeight;
3534
private Storyboard? _currentSb;
3635

37-
private NativeApi.POINT? _lastActivatePosition;
38-
3936
private readonly IRpcController _rpcController;
4037
private readonly ICredentialManager _credentialManager;
4138
private readonly ISyncSessionController _syncSessionController;
@@ -60,7 +57,6 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
6057

6158
InitializeComponent();
6259
AppWindow.Hide();
63-
SystemBackdrop = new DesktopAcrylicBackdrop();
6460
Activated += Window_Activated;
6561
RootFrame.SizeChanged += RootFrame_SizeChanged;
6662

@@ -97,18 +93,18 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
9793
WindowNative.GetWindowHandle(this)));
9894
SizeProxy.SizeChanged += (_, e) =>
9995
{
100-
if (_currentSb is null) return; // nothing running
96+
if (_currentSb is null) return; // nothing running
10197

102-
int newHeight = (int)Math.Round(
98+
var newHeight = (int)Math.Round(
10399
e.NewSize.Height * DisplayScale.WindowScale(this));
104100

105-
int delta = newHeight - _lastWindowHeight;
101+
var delta = newHeight - _lastWindowHeight;
106102
if (delta == 0) return;
107103

108104
var pos = _aw.Position;
109105
var size = _aw.Size;
110106

111-
pos.Y -= delta; // grow upward
107+
pos.Y -= delta; // grow upward
112108
size.Height = newHeight;
113109

114110
_aw.MoveAndResize(
@@ -118,7 +114,6 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
118114
};
119115
}
120116

121-
122117
private void SetPageByState(RpcModel rpcModel, CredentialModel credentialModel,
123118
SyncSessionControllerStateModel syncSessionModel)
124119
{
@@ -225,7 +220,6 @@ private void OnStoryboardCompleted(object? sender, object e)
225220

226221
private void MoveResizeAndActivate()
227222
{
228-
SaveCursorPos();
229223
var size = CalculateWindowSize(RootFrame.GetContentSize().Height);
230224
var pos = CalculateWindowPosition(size);
231225
var rect = new RectInt32(pos.X, pos.Y, size.Width, size.Height);
@@ -234,18 +228,6 @@ private void MoveResizeAndActivate()
234228
NativeApi.SetForegroundWindow(WindowNative.GetWindowHandle(this));
235229
}
236230

237-
private void SaveCursorPos()
238-
{
239-
var res = NativeApi.GetCursorPos(out var cursorPosition);
240-
if (res)
241-
_lastActivatePosition = cursorPosition;
242-
else
243-
// When the cursor position is null, we will spawn the window in
244-
// the bottom right corner of the primary display.
245-
// TODO: log(?) an error when this happens
246-
_lastActivatePosition = null;
247-
}
248-
249231
private SizeInt32 CalculateWindowSize(double height)
250232
{
251233
if (height <= 0) height = 100; // will be resolved next frame typically
@@ -257,41 +239,38 @@ private SizeInt32 CalculateWindowSize(double height)
257239
return new SizeInt32(newWidth, newHeight);
258240
}
259241

260-
private PointInt32 CalculateWindowPosition(SizeInt32 size)
242+
private PointInt32 CalculateWindowPosition(SizeInt32 panelSize)
261243
{
262-
var width = size.Width;
263-
var height = size.Height;
244+
var area = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Primary);
245+
var bounds = area.OuterBounds; // entire monitor rect
246+
var workArea = area.WorkArea; // monitor minus taskbar (and other app bars)
264247

265-
var cursorPosition = _lastActivatePosition;
266-
if (cursorPosition is null)
267-
{
268-
var primaryWorkArea = DisplayArea.Primary.WorkArea;
269-
return new PointInt32(
270-
primaryWorkArea.Width - width,
271-
primaryWorkArea.Height - height
272-
);
273-
}
274-
275-
// Spawn the window to the top right of the cursor.
276-
var x = cursorPosition.Value.X + 10;
277-
var y = cursorPosition.Value.Y - 10 - height;
248+
var tb = GetTaskbarInfo(area);
278249

279-
var workArea = DisplayArea.GetFromPoint(
280-
new PointInt32(cursorPosition.Value.X, cursorPosition.Value.Y),
281-
DisplayAreaFallback.Primary
282-
).WorkArea;
283-
284-
// Adjust if the window goes off the right edge of the display.
285-
if (x + width > workArea.X + workArea.Width) x = workArea.X + workArea.Width - width;
250+
int x, y;
251+
switch (tb.Position)
252+
{
253+
case TaskbarPosition.Left:
254+
x = bounds.X + tb.Gap;
255+
y = workArea.Y + workArea.Height - panelSize.Height;
256+
break;
286257

287-
// Adjust if the window goes off the bottom edge of the display.
288-
if (y + height > workArea.Y + workArea.Height) y = workArea.Y + workArea.Height - height;
258+
case TaskbarPosition.Top:
259+
x = workArea.X + workArea.Width - panelSize.Width;
260+
y = bounds.Y + tb.Gap;
261+
break;
289262

290-
// Adjust if the window goes off the left edge of the display (somehow).
291-
if (x < workArea.X) x = workArea.X;
263+
case TaskbarPosition.Bottom when tb.AutoHide:
264+
// Auto-hide bottom bar sits under the workArea – use workArea, not bounds.
265+
x = workArea.X + workArea.Width - panelSize.Width;
266+
y = workArea.Y + workArea.Height - panelSize.Height - tb.Gap;
267+
break;
292268

293-
// Adjust if the window goes off the top edge of the display (somehow).
294-
if (y < workArea.Y) y = workArea.Y;
269+
default: // right or bottom when not auto-hiding
270+
x = workArea.X + workArea.Width - panelSize.Width;
271+
y = bounds.Y + bounds.Height - panelSize.Height - tb.Gap;
272+
break;
273+
}
295274

296275
return new PointInt32(x, y);
297276
}
@@ -338,4 +317,70 @@ public struct POINT
338317
public int Y;
339318
}
340319
}
320+
internal enum TaskbarPosition { Left, Top, Right, Bottom }
321+
322+
internal readonly record struct TaskbarInfo(TaskbarPosition Position, int Gap, bool AutoHide);
323+
324+
// -----------------------------------------------------------------------------
325+
// Taskbar helpers – ABM_GETTASKBARPOS / ABM_GETSTATE via SHAppBarMessage
326+
// -----------------------------------------------------------------------------
327+
private static TaskbarInfo GetTaskbarInfo(DisplayArea area)
328+
{
329+
var data = new APPBARDATA
330+
{
331+
cbSize = (uint)Marshal.SizeOf<APPBARDATA>()
332+
};
333+
334+
// Locate the taskbar.
335+
if (SHAppBarMessage(ABM_GETTASKBARPOS, ref data) == 0)
336+
return new TaskbarInfo(TaskbarPosition.Bottom, 0, false); // failsafe
337+
338+
var autoHide = (SHAppBarMessage(ABM_GETSTATE, ref data) & ABS_AUTOHIDE) != 0;
339+
340+
// Use uEdge instead of guessing from the RECT.
341+
var pos = data.uEdge switch
342+
{
343+
ABE_LEFT => TaskbarPosition.Left,
344+
ABE_TOP => TaskbarPosition.Top,
345+
ABE_RIGHT => TaskbarPosition.Right,
346+
_ => TaskbarPosition.Bottom, // ABE_BOTTOM or anything unexpected
347+
};
348+
349+
// Thickness (gap) = shorter side of the rect.
350+
var gap = (pos == TaskbarPosition.Left || pos == TaskbarPosition.Right)
351+
? data.rc.right - data.rc.left // width
352+
: data.rc.bottom - data.rc.top; // height
353+
354+
return new TaskbarInfo(pos, gap, autoHide);
355+
}
356+
357+
// ------------- P/Invoke plumbing -------------
358+
private const uint ABM_GETTASKBARPOS = 0x0005;
359+
private const uint ABM_GETSTATE = 0x0004;
360+
private const int ABS_AUTOHIDE = 0x0001;
361+
362+
private const int ABE_LEFT = 0; // values returned in APPBARDATA.uEdge
363+
private const int ABE_TOP = 1;
364+
private const int ABE_RIGHT = 2;
365+
private const int ABE_BOTTOM = 3;
366+
367+
[StructLayout(LayoutKind.Sequential)]
368+
private struct APPBARDATA
369+
{
370+
public uint cbSize;
371+
public IntPtr hWnd;
372+
public uint uCallbackMessage;
373+
public uint uEdge; // contains ABE_* value
374+
public RECT rc;
375+
public int lParam;
376+
}
377+
378+
[StructLayout(LayoutKind.Sequential)]
379+
private struct RECT
380+
{
381+
public int left, top, right, bottom;
382+
}
383+
384+
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
385+
private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
341386
}

0 commit comments

Comments
 (0)