Skip to content

Commit c2a748e

Browse files
committed
feat: calculate taskbar position and display on top of it
1 parent cf25db5 commit c2a748e

File tree

1 file changed

+105
-50
lines changed

1 file changed

+105
-50
lines changed

App/Views/TrayWindow.xaml.cs

Lines changed: 105 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ public sealed partial class TrayWindow : Window
3333
private int _lastWindowHeight;
3434
private Storyboard? _currentSb;
3535

36-
private NativeApi.POINT? _lastActivatePosition;
37-
3836
private readonly IRpcController _rpcController;
3937
private readonly ICredentialManager _credentialManager;
4038
private readonly ISyncSessionController _syncSessionController;
@@ -98,18 +96,18 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
9896
WindowNative.GetWindowHandle(this)));
9997
SizeProxy.SizeChanged += (_, e) =>
10098
{
101-
if (_currentSb is null) return; // nothing running
99+
if (_currentSb is null) return; // nothing running
102100

103-
int newHeight = (int)Math.Round(
101+
var newHeight = (int)Math.Round(
104102
e.NewSize.Height * DisplayScale.WindowScale(this));
105103

106-
int delta = newHeight - _lastWindowHeight;
104+
var delta = newHeight - _lastWindowHeight;
107105
if (delta == 0) return;
108106

109107
var pos = _aw.Position;
110108
var size = _aw.Size;
111109

112-
pos.Y -= delta; // grow upward
110+
pos.Y -= delta; // grow upward
113111
size.Height = newHeight;
114112

115113
_aw.MoveAndResize(
@@ -225,7 +223,6 @@ private void OnStoryboardCompleted(object? sender, object e)
225223

226224
private void MoveResizeAndActivate()
227225
{
228-
SaveCursorPos();
229226
var size = CalculateWindowSize(RootFrame.GetContentSize().Height);
230227
var pos = CalculateWindowPosition(size);
231228
var rect = new RectInt32(pos.X, pos.Y, size.Width, size.Height);
@@ -234,18 +231,6 @@ private void MoveResizeAndActivate()
234231
ForegroundWindow.MakeForeground(this);
235232
}
236233

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-
249234
private SizeInt32 CalculateWindowSize(double height)
250235
{
251236
if (height <= 0) height = 100; // will be resolved next frame typically
@@ -257,41 +242,44 @@ private SizeInt32 CalculateWindowSize(double height)
257242
return new SizeInt32(newWidth, newHeight);
258243
}
259244

260-
private PointInt32 CalculateWindowPosition(SizeInt32 size)
245+
private PointInt32 CalculateWindowPosition(SizeInt32 panelSize)
261246
{
262-
var width = size.Width;
263-
var height = size.Height;
264-
265-
var cursorPosition = _lastActivatePosition;
266-
if (cursorPosition is null)
247+
var area = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Primary);
248+
// whole monitor
249+
var bounds = area.OuterBounds;
250+
// monitor minus taskbar
251+
var workArea = area.WorkArea;
252+
253+
// get taskbar details - position, gap (size), auto-hide
254+
var tb = GetTaskbarInfo(area);
255+
256+
// safe edges where tray window can touch the screen
257+
var safeRight = workArea.X + workArea.Width;
258+
var safeBottom = workArea.Y + workArea.Height;
259+
260+
// if the taskbar is auto-hidden at the bottom, stay clear of its reveal band
261+
if (tb.Position == TaskbarPosition.Bottom && tb.AutoHide)
262+
safeBottom -= tb.Gap; // shift everything up by its thickness
263+
264+
// pick corner & position the panel
265+
int x, y;
266+
switch (tb.Position)
267267
{
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;
278-
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;
286-
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;
268+
case TaskbarPosition.Left: // for Left we will stick to the left-bottom corner
269+
x = bounds.X + tb.Gap; // just right of the bar
270+
y = safeBottom - panelSize.Height;
271+
break;
289272

290-
// Adjust if the window goes off the left edge of the display (somehow).
291-
if (x < workArea.X) x = workArea.X;
273+
case TaskbarPosition.Top: // for Top we will stick to the top-right corner
274+
x = safeRight - panelSize.Width;
275+
y = bounds.Y + tb.Gap; // just below the bar
276+
break;
292277

293-
// Adjust if the window goes off the top edge of the display (somehow).
294-
if (y < workArea.Y) y = workArea.Y;
278+
default: // Bottom or Right bar we will stick to the bottom-right corner
279+
x = safeRight - panelSize.Width;
280+
y = safeBottom - panelSize.Height;
281+
break;
282+
}
295283

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy