From 5638bf1e049c5f3e0ae2e657ed7e9121587544d7 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 11 Jun 2025 15:01:58 +0200 Subject: [PATCH] feat: calculate taskbar position and display on top of it --- App/Views/TrayWindow.xaml.cs | 149 +++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 52 deletions(-) diff --git a/App/Views/TrayWindow.xaml.cs b/App/Views/TrayWindow.xaml.cs index 7ecd75c..ae72b6d 100644 --- a/App/Views/TrayWindow.xaml.cs +++ b/App/Views/TrayWindow.xaml.cs @@ -9,10 +9,9 @@ using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Animation; using System; -using System.Diagnostics; +using System.Drawing.Printing; using System.Runtime.InteropServices; using Windows.Graphics; using Windows.System; @@ -34,8 +33,6 @@ public sealed partial class TrayWindow : Window private int _lastWindowHeight; private Storyboard? _currentSb; - private NativeApi.POINT? _lastActivatePosition; - private readonly IRpcController _rpcController; private readonly ICredentialManager _credentialManager; private readonly ISyncSessionController _syncSessionController; @@ -60,7 +57,6 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan InitializeComponent(); AppWindow.Hide(); - SystemBackdrop = new DesktopAcrylicBackdrop(); Activated += Window_Activated; RootFrame.SizeChanged += RootFrame_SizeChanged; @@ -97,18 +93,18 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan WindowNative.GetWindowHandle(this))); SizeProxy.SizeChanged += (_, e) => { - if (_currentSb is null) return; // nothing running + if (_currentSb is null) return; // nothing running - int newHeight = (int)Math.Round( + var newHeight = (int)Math.Round( e.NewSize.Height * DisplayScale.WindowScale(this)); - int delta = newHeight - _lastWindowHeight; + var delta = newHeight - _lastWindowHeight; if (delta == 0) return; var pos = _aw.Position; var size = _aw.Size; - pos.Y -= delta; // grow upward + pos.Y -= delta; // grow upward size.Height = newHeight; _aw.MoveAndResize( @@ -118,7 +114,6 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan }; } - private void SetPageByState(RpcModel rpcModel, CredentialModel credentialModel, SyncSessionControllerStateModel syncSessionModel) { @@ -225,7 +220,6 @@ private void OnStoryboardCompleted(object? sender, object e) private void MoveResizeAndActivate() { - SaveCursorPos(); var size = CalculateWindowSize(RootFrame.GetContentSize().Height); var pos = CalculateWindowPosition(size); var rect = new RectInt32(pos.X, pos.Y, size.Width, size.Height); @@ -234,18 +228,6 @@ private void MoveResizeAndActivate() NativeApi.SetForegroundWindow(WindowNative.GetWindowHandle(this)); } - private void SaveCursorPos() - { - var res = NativeApi.GetCursorPos(out var cursorPosition); - if (res) - _lastActivatePosition = cursorPosition; - else - // When the cursor position is null, we will spawn the window in - // the bottom right corner of the primary display. - // TODO: log(?) an error when this happens - _lastActivatePosition = null; - } - private SizeInt32 CalculateWindowSize(double height) { if (height <= 0) height = 100; // will be resolved next frame typically @@ -257,41 +239,38 @@ private SizeInt32 CalculateWindowSize(double height) return new SizeInt32(newWidth, newHeight); } - private PointInt32 CalculateWindowPosition(SizeInt32 size) + private PointInt32 CalculateWindowPosition(SizeInt32 panelSize) { - var width = size.Width; - var height = size.Height; + var area = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Primary); + var bounds = area.OuterBounds; // entire monitor rect + var workArea = area.WorkArea; // monitor minus taskbar (and other app bars) - var cursorPosition = _lastActivatePosition; - if (cursorPosition is null) - { - var primaryWorkArea = DisplayArea.Primary.WorkArea; - return new PointInt32( - primaryWorkArea.Width - width, - primaryWorkArea.Height - height - ); - } - - // Spawn the window to the top right of the cursor. - var x = cursorPosition.Value.X + 10; - var y = cursorPosition.Value.Y - 10 - height; + var tb = GetTaskbarInfo(area); - var workArea = DisplayArea.GetFromPoint( - new PointInt32(cursorPosition.Value.X, cursorPosition.Value.Y), - DisplayAreaFallback.Primary - ).WorkArea; - - // Adjust if the window goes off the right edge of the display. - if (x + width > workArea.X + workArea.Width) x = workArea.X + workArea.Width - width; + int x, y; + switch (tb.Position) + { + case TaskbarPosition.Left: + x = bounds.X + tb.Gap; + y = workArea.Y + workArea.Height - panelSize.Height; + break; - // Adjust if the window goes off the bottom edge of the display. - if (y + height > workArea.Y + workArea.Height) y = workArea.Y + workArea.Height - height; + case TaskbarPosition.Top: + x = workArea.X + workArea.Width - panelSize.Width; + y = bounds.Y + tb.Gap; + break; - // Adjust if the window goes off the left edge of the display (somehow). - if (x < workArea.X) x = workArea.X; + case TaskbarPosition.Bottom when tb.AutoHide: + // Auto-hide bottom bar sits under the workArea – use workArea, not bounds. + x = workArea.X + workArea.Width - panelSize.Width; + y = workArea.Y + workArea.Height - panelSize.Height - tb.Gap; + break; - // Adjust if the window goes off the top edge of the display (somehow). - if (y < workArea.Y) y = workArea.Y; + default: // right or bottom when not auto-hiding + x = workArea.X + workArea.Width - panelSize.Width; + y = bounds.Y + bounds.Height - panelSize.Height - tb.Gap; + break; + } return new PointInt32(x, y); } @@ -338,4 +317,70 @@ public struct POINT public int Y; } } + internal enum TaskbarPosition { Left, Top, Right, Bottom } + + internal readonly record struct TaskbarInfo(TaskbarPosition Position, int Gap, bool AutoHide); + + // ----------------------------------------------------------------------------- + // Taskbar helpers – ABM_GETTASKBARPOS / ABM_GETSTATE via SHAppBarMessage + // ----------------------------------------------------------------------------- + private static TaskbarInfo GetTaskbarInfo(DisplayArea area) + { + var data = new APPBARDATA + { + cbSize = (uint)Marshal.SizeOf() + }; + + // Locate the taskbar. + if (SHAppBarMessage(ABM_GETTASKBARPOS, ref data) == 0) + return new TaskbarInfo(TaskbarPosition.Bottom, 0, false); // failsafe + + var autoHide = (SHAppBarMessage(ABM_GETSTATE, ref data) & ABS_AUTOHIDE) != 0; + + // Use uEdge instead of guessing from the RECT. + var pos = data.uEdge switch + { + ABE_LEFT => TaskbarPosition.Left, + ABE_TOP => TaskbarPosition.Top, + ABE_RIGHT => TaskbarPosition.Right, + _ => TaskbarPosition.Bottom, // ABE_BOTTOM or anything unexpected + }; + + // Thickness (gap) = shorter side of the rect. + var gap = (pos == TaskbarPosition.Left || pos == TaskbarPosition.Right) + ? data.rc.right - data.rc.left // width + : data.rc.bottom - data.rc.top; // height + + return new TaskbarInfo(pos, gap, autoHide); + } + + // ------------- P/Invoke plumbing ------------- + private const uint ABM_GETTASKBARPOS = 0x0005; + private const uint ABM_GETSTATE = 0x0004; + private const int ABS_AUTOHIDE = 0x0001; + + private const int ABE_LEFT = 0; // values returned in APPBARDATA.uEdge + private const int ABE_TOP = 1; + private const int ABE_RIGHT = 2; + private const int ABE_BOTTOM = 3; + + [StructLayout(LayoutKind.Sequential)] + private struct APPBARDATA + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public uint uEdge; // contains ABE_* value + public RECT rc; + public int lParam; + } + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int left, top, right, bottom; + } + + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData); }