From 11fbb0b847a3b33e98b3a4dae9aed32eab1f2b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Fri, 3 Jul 2026 20:32:51 -0500 Subject: [PATCH 1/4] Replace low-level mouse drag hook Handle taskbar repositioning and resizing through WPF mouse events instead of the low-level mouse hook. The taskbar now captures mouse input directly, updates edge changes and resize steps during drag, and removes the obsolete hook utility. --- RetroBar/Taskbar.xaml | 3 +- RetroBar/Taskbar.xaml.cs | 291 ++++++++++++------------ RetroBar/Utilities/LowLevelMouseHook.cs | 102 --------- 3 files changed, 145 insertions(+), 251 deletions(-) delete mode 100644 RetroBar/Utilities/LowLevelMouseHook.cs diff --git a/RetroBar/Taskbar.xaml b/RetroBar/Taskbar.xaml index f61c4c44..53d45c7d 100644 --- a/RetroBar/Taskbar.xaml +++ b/RetroBar/Taskbar.xaml @@ -10,7 +10,8 @@ LocationChanged="Taskbar_OnLocationChanged" SizeChanged="Taskbar_OnSizeChanged" MouseLeftButtonDown="Taskbar_OnMouseLeftButtonDown" - MouseMove="Taskbar_MouseMove" + MouseLeftButtonUp="Taskbar_OnMouseLeftButtonUp" + MouseMove="Taskbar_OnMouseMove" Deactivated="Taskbar_Deactivated" AllowDrop="True" Style="{DynamicResource TaskbarWindow}"> diff --git a/RetroBar/Taskbar.xaml.cs b/RetroBar/Taskbar.xaml.cs index 3853e105..ed981f74 100644 --- a/RetroBar/Taskbar.xaml.cs +++ b/RetroBar/Taskbar.xaml.cs @@ -34,8 +34,8 @@ public int Rows } private bool _startMenuOpen; - private LowLevelMouseHook _mouseDragHook; private Point? _mouseDragStart = null; + private bool _isDragging; private bool _mouseDragResize = false; private readonly DictionaryManager _dictionaryManager; private readonly ShellManager _shellManager; @@ -243,13 +243,13 @@ protected override void OnSourceInitialized(object sender, EventArgs e) SetBlur(AllowsBlur()); UpdateTrayPosition(); } - + protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { base.WndProc(hwnd, msg, wParam, lParam, ref handled); - if ((msg == (int)NativeMethods.WM.SYSCOLORCHANGE || - msg == (int)NativeMethods.WM.SETTINGCHANGE) && + if ((msg == (int)NativeMethods.WM.SYSCOLORCHANGE || + msg == (int)NativeMethods.WM.SETTINGCHANGE) && Settings.Instance.Theme.StartsWith(DictionaryManager.THEME_DEFAULT)) { handled = true; @@ -572,15 +572,124 @@ private bool AllowsBlur() (Application.Current.FindResource("AllowsTransparency") as bool? ?? false); } - #region Unlocked taskbar drag hook + #region Unlocked taskbar drag private void Taskbar_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (!IsLocked) + if (IsLocked) return; + + var screenPos = PointToScreen(e.GetPosition(this)); + + // if mouse is in resize‐zone, begin resize drag + if (IsMouseInResizeArea()) + { + _mouseDragResize = true; + Mouse.Capture(this); + return; + } + + // otherwise begin reposition drag + _mouseDragStart = PointToScreen(e.GetPosition(this)); + _isDragging = true; + Mouse.Capture(this); + } + + private void Taskbar_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (_mouseDragResize) + { + _mouseDragResize = false; + Mouse.Capture(null); + return; + } + + if (!_isDragging) return; + + _isDragging = false; + _mouseDragStart = null; + Mouse.Capture(null); + } + + private void Taskbar_OnMouseMove(object sender, MouseEventArgs e) + { + // Show resize cursor for resizable taskbars + if (_mouseDragResize || (!_isDragging && IsMouseInResizeArea())) + { + Cursor = Orientation == Orientation.Horizontal ? Cursors.SizeNS : Cursors.SizeWE; + } + else { - // Start low-level mouse hook to receive current drag position - // The hook should be stopped upon mouse up - StartMouseDragHook(); + Cursor = Cursors.Arrow; } + + if (_mouseDragResize) + { + // Use WPF's Mouse position instead of WinForms + Point cursorPosition = PointToScreen(Mouse.GetPosition(this)); + int mouseX = (int)cursorPosition.X; + int mouseY = (int)cursorPosition.Y; + + // Process resize operation directly instead of using BeginInvoke + // This avoids dispatcher overhead and potential lag when Explorer is not running + double scaledRowHeight = DesiredRowHeight * DpiScale; + + if (Orientation == Orientation.Horizontal) + { + // Use Screen reference instead of PrimaryScreen to handle multi-monitor setups correctly + double taskbarEdge = (AppBarEdge == AppBarEdge.Top + ? Screen.Bounds.Top + (DesiredHeight * DpiScale) + : Screen.Bounds.Bottom - (DesiredHeight * DpiScale) + ); + + if ((AppBarEdge == AppBarEdge.Top && mouseY < taskbarEdge - SystemParameters.MinimumVerticalDragDistance + || AppBarEdge == AppBarEdge.Bottom && mouseY > taskbarEdge + SystemParameters.MinimumVerticalDragDistance) + && Settings.Instance.RowCount > 1) + { + Settings.Instance.RowCount--; + } + else if ((AppBarEdge == AppBarEdge.Top && mouseY >= taskbarEdge + scaledRowHeight + || AppBarEdge == AppBarEdge.Bottom && mouseY <= taskbarEdge - scaledRowHeight) + && Settings.Instance.RowCount < Settings.Instance.RowLimit) + { + Settings.Instance.RowCount++; + } + } + else + { + double taskbarEdge = (AppBarEdge == AppBarEdge.Left + ? Screen.Bounds.Left + (DesiredWidth * DpiScale) + : Screen.Bounds.Right - (DesiredWidth * DpiScale) + ); + + if ((AppBarEdge == AppBarEdge.Left && mouseX > taskbarEdge + scaledRowHeight + || AppBarEdge == AppBarEdge.Right && mouseX < taskbarEdge - scaledRowHeight) + && Settings.Instance.TaskbarWidth < Settings.Instance.TaskbarWidthLimit) + { + Settings.Instance.TaskbarWidth++; + } + else if ((AppBarEdge == AppBarEdge.Left && mouseX < taskbarEdge - SystemParameters.MinimumHorizontalDragDistance + || AppBarEdge == AppBarEdge.Right && mouseX > taskbarEdge + SystemParameters.MinimumHorizontalDragDistance) + && Settings.Instance.TaskbarWidth > 1) + { + Settings.Instance.TaskbarWidth--; + } + } + return; + } + + // reposition‐while‐dragging + if (!_isDragging || _mouseDragStart == null) + return; + + // only start moving edge after system drag threshold + var screenPos = PointToScreen(e.GetPosition(this)); + if (Math.Abs(screenPos.X - _mouseDragStart.Value.X) <= SystemParameters.MinimumHorizontalDragDistance && + Math.Abs(screenPos.Y - _mouseDragStart.Value.Y) <= SystemParameters.MinimumVerticalDragDistance) + return; + + var newEdge = DragCoordsToScreenEdge((int)screenPos.X, (int)screenPos.Y); + + if (newEdge != AppBarEdge) + Settings.Instance.Edge = newEdge; } private AppBarEdge DragCoordsToScreenEdge(int x, int y) @@ -649,151 +758,37 @@ private AppBarEdge DragCoordsToScreenEdge(int x, int y) } } - private void MouseDragHook_LowLevelMouseEvent(object sender, LowLevelMouseHook.LowLevelMouseEventArgs e) - { - switch (e.Message) - { - case NativeMethods.WM.MOUSEMOVE: - if (_mouseDragStart == null) - { - return; - } - - if (_mouseDragResize) - { - Dispatcher.BeginInvoke(() => { - int mouseX = e.HookStruct.pt.X; - int mouseY = e.HookStruct.pt.Y; - // Calculate where the resize edge should be, in case the actual resize operation is lagging behind the mouse - double scaledRowHeight = DesiredRowHeight * DpiScale; - if (Orientation == Orientation.Horizontal) - { - double taskbarEdge = AppBarEdge == AppBarEdge.Top ? Screen.Bounds.Top + (DesiredHeight * DpiScale) : Screen.Bounds.Bottom - (DesiredHeight * DpiScale); - if ((AppBarEdge == AppBarEdge.Top && mouseY < taskbarEdge - SystemParameters.MinimumVerticalDragDistance || - AppBarEdge == AppBarEdge.Bottom && mouseY > taskbarEdge + SystemParameters.MinimumVerticalDragDistance) && - Settings.Instance.RowCount > 1) - { - // If mouse is inside the taskbar and more than the minimum drag distance away, decrement size - Settings.Instance.RowCount -= 1; - } - else if ((AppBarEdge == AppBarEdge.Top && mouseY >= taskbarEdge + scaledRowHeight || - AppBarEdge == AppBarEdge.Bottom && mouseY <= taskbarEdge - scaledRowHeight) && - Settings.Instance.RowCount < Settings.Instance.RowLimit) - { - // If mouse is outside the taskbar and at least one row height away, increment size - Settings.Instance.RowCount += 1; - } - } - else - { - double taskbarEdge = AppBarEdge == AppBarEdge.Left ? Screen.Bounds.Left + (DesiredWidth * DpiScale) : Screen.Bounds.Right - (DesiredWidth * DpiScale); - if ((AppBarEdge == AppBarEdge.Left && mouseX > taskbarEdge + scaledRowHeight || - AppBarEdge == AppBarEdge.Right && mouseX < taskbarEdge - scaledRowHeight) && - Settings.Instance.TaskbarWidth < Settings.Instance.TaskbarWidthLimit) - { - Settings.Instance.TaskbarWidth += 1; - } - else if ((AppBarEdge == AppBarEdge.Left && mouseX < taskbarEdge - SystemParameters.MinimumHorizontalDragDistance || - AppBarEdge == AppBarEdge.Right && mouseX > taskbarEdge + SystemParameters.MinimumHorizontalDragDistance) && - Settings.Instance.TaskbarWidth > 1) - { - Settings.Instance.TaskbarWidth -= 1; - } - } - }); - return; - } - - if (Math.Abs(e.HookStruct.pt.X - (double)(_mouseDragStart?.X)) <= SystemParameters.MinimumHorizontalDragDistance || - Math.Abs(e.HookStruct.pt.Y - (double)(_mouseDragStart?.Y)) <= SystemParameters.MinimumVerticalDragDistance) - { - return; - } - - AppBarEdge newEdge = DragCoordsToScreenEdge(e.HookStruct.pt.X, e.HookStruct.pt.Y); - if (newEdge != AppBarEdge) - { - Settings.Instance.Edge = newEdge; - } - break; - case NativeMethods.WM.LBUTTONUP: - case NativeMethods.WM.LBUTTONDOWN: - case NativeMethods.WM.MBUTTONUP: - case NativeMethods.WM.MBUTTONDOWN: - case NativeMethods.WM.RBUTTONUP: - case NativeMethods.WM.RBUTTONDOWN: - case NativeMethods.WM.XBUTTONUP: - case NativeMethods.WM.XBUTTONDOWN: - StopMouseDragHook(); - break; - } - } - - private void StartMouseDragHook() - { - if (_mouseDragHook != null) - { - return; - } - - _mouseDragHook = new LowLevelMouseHook(); - _mouseDragHook.LowLevelMouseEvent += MouseDragHook_LowLevelMouseEvent; - _mouseDragHook.Initialize(); - _mouseDragStart = new Point(System.Windows.Forms.Cursor.Position.X, System.Windows.Forms.Cursor.Position.Y); - _mouseDragResize = IsMouseInResizeArea(); - - ShellLogger.Debug($"Mouse drag hook started"); - } - - private void StopMouseDragHook() - { - _mouseDragHook.LowLevelMouseEvent -= MouseDragHook_LowLevelMouseEvent; - _mouseDragHook.Dispose(); - _mouseDragHook = null; - _mouseDragStart = null; - _mouseDragResize = false; - - ShellLogger.Debug("Mouse drag hook removed"); - } - private bool IsMouseInResizeArea() { if (IsLocked) return false; + // Calculate resize region size once int resizeRegionSize = (int)((_unlockedMargin > 0 ? _unlockedMargin : SystemParameters.MinimumVerticalDragDistance * Settings.Instance.TaskbarScale) * DpiScale); - int mouseX = System.Windows.Forms.Cursor.Position.X; - int mouseY = System.Windows.Forms.Cursor.Position.Y; - if (AppBarEdge == AppBarEdge.Bottom && mouseY <= (int)(Top * DpiScale) + resizeRegionSize) - { - return true; - } - else if (AppBarEdge == AppBarEdge.Top && mouseY >= (int)((Top + Height) * DpiScale) - resizeRegionSize) - { - return true; - } - else if (AppBarEdge == AppBarEdge.Left && mouseX >= (int)((Left + Width) * DpiScale) - resizeRegionSize) - { - return true; - } - else if (AppBarEdge == AppBarEdge.Right && mouseX <= (int)(Left * DpiScale) + resizeRegionSize) - { - return true; - } - - return false; - } - - private void Taskbar_MouseMove(object sender, MouseEventArgs e) - { - // Show resize cursor for resizable taskbars - if (IsMouseInResizeArea() || _mouseDragResize) - { - Cursor = Orientation == Orientation.Horizontal ? Cursors.SizeNS : Cursors.SizeWE; - } - else - { - Cursor = Cursors.Arrow; + // Get cursor position using WPF's Mouse class instead of System.Windows.Forms.Cursor + Point cursorPos = PointToScreen(Mouse.GetPosition(this)); + int mouseX = (int)cursorPos.X; + int mouseY = (int)cursorPos.Y; + + // Create boundary rectangles based on edge position + int scaledTop = (int)(Top * DpiScale); + int scaledLeft = (int)(Left * DpiScale); + int scaledBottom = (int)((Top + Height) * DpiScale); + int scaledRight = (int)((Left + Width) * DpiScale); + + // Check if mouse is in resize area based on current edge + switch (AppBarEdge) + { + case AppBarEdge.Bottom: + return mouseY <= scaledTop + resizeRegionSize; + case AppBarEdge.Top: + return mouseY >= scaledBottom - resizeRegionSize; + case AppBarEdge.Left: + return mouseX >= scaledRight - resizeRegionSize; + case AppBarEdge.Right: + return mouseX <= scaledLeft + resizeRegionSize; + default: + return false; } } #endregion diff --git a/RetroBar/Utilities/LowLevelMouseHook.cs b/RetroBar/Utilities/LowLevelMouseHook.cs deleted file mode 100644 index 550b22c3..00000000 --- a/RetroBar/Utilities/LowLevelMouseHook.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.InteropServices; -using static ManagedShell.Interop.NativeMethods; - -namespace RetroBar.Utilities -{ - // TODO: This should move to ManagedShell - public class LowLevelMouseHook : IDisposable - { - [DllImport("user32.dll")] - public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProcDelegate callback, IntPtr hInstance, uint threadId); - - [DllImport("user32.dll")] - public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, uint wParam, IntPtr lParam); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern IntPtr GetModuleHandle(string lpModuleName); - - public delegate IntPtr LowLevelMouseProcDelegate(int code, uint wParam, IntPtr lParam); - - const int WH_MOUSE_LL = 14; - - [StructLayout(LayoutKind.Sequential)] - public struct MSLLHOOKSTRUCT - { - public POINT pt; - public int mouseData; - public int flags; - public int time; - public UIntPtr dwExtraInfo; - } - - [StructLayout(LayoutKind.Sequential)] - public struct POINT - { - public int X; - public int Y; - } - - public class LowLevelMouseEventArgs : HandledEventArgs - { - public WM Message; - public MSLLHOOKSTRUCT HookStruct; - } - - public event EventHandler LowLevelMouseEvent; - - private IntPtr _hook = IntPtr.Zero; - private LowLevelMouseProcDelegate _hookDelegate; - - public LowLevelMouseHook() { - _hookDelegate = MouseHookProc; - } - - public bool Initialize() - { - using (Process curProcess = Process.GetCurrentProcess()) - using (ProcessModule curModule = curProcess.MainModule) - { - _hook = SetWindowsHookEx(WH_MOUSE_LL, _hookDelegate, GetModuleHandle(curModule.ModuleName), 0); - - if (_hook == IntPtr.Zero) - { - return false; - } - - return true; - } - } - - private IntPtr MouseHookProc(int code, uint wParam, IntPtr lParam) - { - LowLevelMouseEventArgs args = new LowLevelMouseEventArgs - { - Message = (WM)wParam, - HookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)) - }; - - LowLevelMouseEvent?.Invoke(this, args); - - if (args.Handled) - { - return (IntPtr)1; - } - - return CallNextHookEx(_hook, code, wParam, lParam); - } - - public void Dispose() - { - if (_hook == IntPtr.Zero) - { - return; - } - - UnhookWindowsHookEx(_hook); - _hook = IntPtr.Zero; - } - } -} From c57e43d769683fe0e97380c8c029b5e952e9c968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Sun, 9 Aug 2026 11:52:35 -0500 Subject: [PATCH 2/4] Add system menu logic --- RetroBar/RetroBar.csproj | 2 +- RetroBar/Taskbar.xaml.cs | 349 +++++++++++++++++++++------- RetroBar/Utilities/WindowManager.cs | 21 ++ 3 files changed, 291 insertions(+), 81 deletions(-) diff --git a/RetroBar/RetroBar.csproj b/RetroBar/RetroBar.csproj index cc029db8..f4aacb3d 100644 --- a/RetroBar/RetroBar.csproj +++ b/RetroBar/RetroBar.csproj @@ -45,7 +45,7 @@ - + diff --git a/RetroBar/Taskbar.xaml.cs b/RetroBar/Taskbar.xaml.cs index ed981f74..8c8c86d6 100644 --- a/RetroBar/Taskbar.xaml.cs +++ b/RetroBar/Taskbar.xaml.cs @@ -1,13 +1,13 @@ using ManagedShell; using ManagedShell.AppBar; using ManagedShell.Common.Helpers; -using ManagedShell.Common.Logging; using ManagedShell.Interop; using ManagedShell.WindowsTray; using RetroBar.Utilities; using System; using System.ComponentModel; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -36,7 +36,10 @@ public int Rows private bool _startMenuOpen; private Point? _mouseDragStart = null; private bool _isDragging; - private bool _mouseDragResize = false; + private bool _mouseDragResize; + private AppBarEdge _dragStartEdge; + private int _dragStartRowCount; + private int _dragStartTaskbarWidth; private readonly DictionaryManager _dictionaryManager; private readonly ShellManager _shellManager; private readonly StartMenuMonitor _startMenuMonitor; @@ -84,6 +87,7 @@ public Taskbar(WindowManager windowManager, DictionaryManager dictionaryManager, AutoHideElement = TaskbarContentControl; PropertyChanged += Taskbar_PropertyChanged; + PreviewKeyDown += Taskbar_PreviewKeyDown; _startMenuMonitor.StartMenuVisibilityChanged += StartMenuMonitor_StartMenuVisibilityChanged; _shellManager.TasksService.WindowActivated += TasksService_WindowActivated; @@ -158,6 +162,7 @@ private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) { PeekDuringAutoHide(); AppBarEdge = Settings.Instance.Edge; + UpdateLayout(); UpdatePosition(); } else if (e.PropertyName == nameof(Settings.Language)) @@ -261,6 +266,77 @@ protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lP { windowManager.NotifyWorkAreaChange(); } + else if (msg == (int)NativeMethods.WM.SYSCHAR && wParam.ToInt32() == ' ') + { + handled = true; + ShowSystemMenu(hwnd); + } + else if (msg == (int)NativeMethods.WM.SYSCOMMAND) + { + int sc = wParam.ToInt32() & 0xFFF0; + if ((sc == NativeMethods.SC_MOVE || sc == NativeMethods.SC_SIZE) && IsLocked) + { + handled = true; + return IntPtr.Zero; + } + else if (sc == NativeMethods.SC_CLOSE) + { + handled = true; + IntPtr progmanHwnd = NativeMethods.FindWindow("Progman", "Program Manager"); + if (progmanHwnd != IntPtr.Zero) + { + NativeMethods.PostMessage(progmanHwnd, (uint)NativeMethods.WM.CLOSE, IntPtr.Zero, IntPtr.Zero); + } + return IntPtr.Zero; + } + } + else if (msg == (int)NativeMethods.WM.ENTERSIZEMOVE) + { + BeginDragOrResize(); + } + else if (msg == (int)NativeMethods.WM.EXITSIZEMOVE) + { + if (NativeMethods.GetAsyncKeyState((int)System.Windows.Forms.Keys.Escape) != 0) + { + CancelDragOrResize(); + } + else + { + windowManager?.NotifyDragEnd(); + } + } + else if (msg == (int)NativeMethods.WM.MOVING) + { + handled = true; + if (NativeMethods.GetCursorPos(out NativeMethods.POINT pt)) + { + var newEdge = DragCoordsToScreenEdge(pt.x, pt.y); + if (newEdge != AppBarEdge) + { + Settings.Instance.Edge = newEdge; + } + } + + var desiredRect = GetDesiredRect(); + Marshal.StructureToPtr(desiredRect, lParam, true); + return (IntPtr)1; + } + else if (msg == (int)NativeMethods.WM.SIZING) + { + handled = true; + if (!IsLocked) + { + if (NativeMethods.GetCursorPos(out NativeMethods.POINT pt)) + { + int currentCoord = (Orientation == Orientation.Horizontal) ? pt.y : pt.x; + ProcessResize(currentCoord); + } + } + + var desiredRect = GetDesiredRect(); + Marshal.StructureToPtr(desiredRect, lParam, true); + return (IntPtr)1; + } return IntPtr.Zero; } @@ -573,40 +649,146 @@ private bool AllowsBlur() } #region Unlocked taskbar drag + private void BeginDragOrResize() + { + _dragStartEdge = AppBarEdge; + _dragStartRowCount = Settings.Instance.RowCount; + _dragStartTaskbarWidth = Settings.Instance.TaskbarWidth; + windowManager?.NotifyDragBegin(); + } + + private void EndDragOrResize() + { + bool wasActive = _isDragging || _mouseDragResize; + _isDragging = false; + _mouseDragResize = false; + _mouseDragStart = null; + Cursor = Cursors.Arrow; + ReleaseMouseCapture(); + + if (wasActive) + { + windowManager?.NotifyDragEnd(); + } + } + + private void CancelDragOrResize() + { + bool wasActive = _isDragging || _mouseDragResize; + _isDragging = false; + _mouseDragResize = false; + _mouseDragStart = null; + Cursor = Cursors.Arrow; + ReleaseMouseCapture(); + + if (Settings.Instance.Edge != _dragStartEdge) + { + Settings.Instance.Edge = _dragStartEdge; + } + if (Settings.Instance.RowCount != _dragStartRowCount) + { + Settings.Instance.RowCount = _dragStartRowCount; + } + if (Settings.Instance.TaskbarWidth != _dragStartTaskbarWidth) + { + Settings.Instance.TaskbarWidth = _dragStartTaskbarWidth; + } + + if (wasActive) + { + windowManager?.NotifyDragEnd(); + } + } + + private void Taskbar_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && (_isDragging || _mouseDragResize)) + { + e.Handled = true; + CancelDragOrResize(); + } + } + + protected override void OnLostMouseCapture(MouseEventArgs e) + { + base.OnLostMouseCapture(e); + if (_isDragging || _mouseDragResize) + { + CancelDragOrResize(); + } + } + private void Taskbar_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (IsLocked) return; - var screenPos = PointToScreen(e.GetPosition(this)); + BeginDragOrResize(); // if mouse is in resize‐zone, begin resize drag if (IsMouseInResizeArea()) { _mouseDragResize = true; - Mouse.Capture(this); + CaptureMouse(); return; } // otherwise begin reposition drag _mouseDragStart = PointToScreen(e.GetPosition(this)); _isDragging = true; - Mouse.Capture(this); + CaptureMouse(); } private void Taskbar_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { - if (_mouseDragResize) + if (_mouseDragResize || _isDragging) { - _mouseDragResize = false; - Mouse.Capture(null); - return; + EndDragOrResize(); } + } - if (!_isDragging) return; + private void ProcessResize(int coordinate) + { + double scaledRowHeight = DesiredRowHeight * DpiScale; + if (scaledRowHeight <= 0) return; - _isDragging = false; - _mouseDragStart = null; - Mouse.Capture(null); + if (Orientation == Orientation.Horizontal) + { + double distance = AppBarEdge == AppBarEdge.Bottom + ? Screen.Bounds.Bottom - coordinate + : coordinate - Screen.Bounds.Top; + + double baseHeight = (Settings.Instance.TaskbarScale * (Application.Current.FindResource("TaskbarHeight") as double? ?? 0)) * DpiScale; + if (AppBarMode == AppBarMode.AutoHide || !Settings.Instance.LockTaskbar) + { + baseHeight += _unlockedMargin; + } + + int targetRows = 1 + (int)Math.Max(0, Math.Round((distance - baseHeight) / scaledRowHeight)); + targetRows = Math.Clamp(targetRows, 1, Settings.Instance.RowLimit); + if (targetRows != Settings.Instance.RowCount) + { + Settings.Instance.RowCount = targetRows; + } + } + else + { + double distance = AppBarEdge == AppBarEdge.Right + ? Screen.Bounds.Right - coordinate + : coordinate - Screen.Bounds.Left; + + double baseWidth = (Settings.Instance.TaskbarScale * (Application.Current.FindResource("TaskbarWidth") as double? ?? 0)) * DpiScale; + if (AppBarMode == AppBarMode.AutoHide || !Settings.Instance.LockTaskbar) + { + baseWidth += _unlockedMargin; + } + + int targetWidth = 1 + (int)Math.Max(0, Math.Round((distance - baseWidth) / scaledRowHeight)); + targetWidth = Math.Clamp(targetWidth, 1, Settings.Instance.TaskbarWidthLimit); + if (targetWidth != Settings.Instance.TaskbarWidth) + { + Settings.Instance.TaskbarWidth = targetWidth; + } + } } private void Taskbar_OnMouseMove(object sender, MouseEventArgs e) @@ -623,56 +805,9 @@ private void Taskbar_OnMouseMove(object sender, MouseEventArgs e) if (_mouseDragResize) { - // Use WPF's Mouse position instead of WinForms Point cursorPosition = PointToScreen(Mouse.GetPosition(this)); - int mouseX = (int)cursorPosition.X; - int mouseY = (int)cursorPosition.Y; - - // Process resize operation directly instead of using BeginInvoke - // This avoids dispatcher overhead and potential lag when Explorer is not running - double scaledRowHeight = DesiredRowHeight * DpiScale; - - if (Orientation == Orientation.Horizontal) - { - // Use Screen reference instead of PrimaryScreen to handle multi-monitor setups correctly - double taskbarEdge = (AppBarEdge == AppBarEdge.Top - ? Screen.Bounds.Top + (DesiredHeight * DpiScale) - : Screen.Bounds.Bottom - (DesiredHeight * DpiScale) - ); - - if ((AppBarEdge == AppBarEdge.Top && mouseY < taskbarEdge - SystemParameters.MinimumVerticalDragDistance - || AppBarEdge == AppBarEdge.Bottom && mouseY > taskbarEdge + SystemParameters.MinimumVerticalDragDistance) - && Settings.Instance.RowCount > 1) - { - Settings.Instance.RowCount--; - } - else if ((AppBarEdge == AppBarEdge.Top && mouseY >= taskbarEdge + scaledRowHeight - || AppBarEdge == AppBarEdge.Bottom && mouseY <= taskbarEdge - scaledRowHeight) - && Settings.Instance.RowCount < Settings.Instance.RowLimit) - { - Settings.Instance.RowCount++; - } - } - else - { - double taskbarEdge = (AppBarEdge == AppBarEdge.Left - ? Screen.Bounds.Left + (DesiredWidth * DpiScale) - : Screen.Bounds.Right - (DesiredWidth * DpiScale) - ); - - if ((AppBarEdge == AppBarEdge.Left && mouseX > taskbarEdge + scaledRowHeight - || AppBarEdge == AppBarEdge.Right && mouseX < taskbarEdge - scaledRowHeight) - && Settings.Instance.TaskbarWidth < Settings.Instance.TaskbarWidthLimit) - { - Settings.Instance.TaskbarWidth++; - } - else if ((AppBarEdge == AppBarEdge.Left && mouseX < taskbarEdge - SystemParameters.MinimumHorizontalDragDistance - || AppBarEdge == AppBarEdge.Right && mouseX > taskbarEdge + SystemParameters.MinimumHorizontalDragDistance) - && Settings.Instance.TaskbarWidth > 1) - { - Settings.Instance.TaskbarWidth--; - } - } + int currentCoord = Orientation == Orientation.Horizontal ? (int)cursorPosition.Y : (int)cursorPosition.X; + ProcessResize(currentCoord); return; } @@ -762,35 +897,89 @@ private bool IsMouseInResizeArea() { if (IsLocked) return false; - // Calculate resize region size once - int resizeRegionSize = (int)((_unlockedMargin > 0 ? _unlockedMargin : SystemParameters.MinimumVerticalDragDistance * Settings.Instance.TaskbarScale) * DpiScale); - - // Get cursor position using WPF's Mouse class instead of System.Windows.Forms.Cursor - Point cursorPos = PointToScreen(Mouse.GetPosition(this)); - int mouseX = (int)cursorPos.X; - int mouseY = (int)cursorPos.Y; - - // Create boundary rectangles based on edge position - int scaledTop = (int)(Top * DpiScale); - int scaledLeft = (int)(Left * DpiScale); - int scaledBottom = (int)((Top + Height) * DpiScale); - int scaledRight = (int)((Left + Width) * DpiScale); + double resizeGrip = _unlockedMargin > 0 ? _unlockedMargin : (SystemParameters.MinimumVerticalDragDistance * Settings.Instance.TaskbarScale); + Point localPos = Mouse.GetPosition(this); - // Check if mouse is in resize area based on current edge switch (AppBarEdge) { case AppBarEdge.Bottom: - return mouseY <= scaledTop + resizeRegionSize; + return localPos.Y >= 0 && localPos.Y <= resizeGrip; case AppBarEdge.Top: - return mouseY >= scaledBottom - resizeRegionSize; + return localPos.Y >= ActualHeight - resizeGrip && localPos.Y <= ActualHeight; case AppBarEdge.Left: - return mouseX >= scaledRight - resizeRegionSize; + return localPos.X >= ActualWidth - resizeGrip && localPos.X <= ActualWidth; case AppBarEdge.Right: - return mouseX <= scaledLeft + resizeRegionSize; + return localPos.X >= 0 && localPos.X <= resizeGrip; default: return false; } } #endregion + + #region System Menu + private void ShowSystemMenu(IntPtr hwnd) + { + int oldStyle = NativeMethods.GetWindowLong(hwnd, NativeMethods.WindowLongFlags.GWL_STYLE); + NativeMethods.SetWindowLong(hwnd, NativeMethods.WindowLongFlags.GWL_STYLE, oldStyle | (int)NativeMethods.WindowStyles.WS_SYSMENU); + + IntPtr hMenu = NativeMethods.GetSystemMenu(hwnd, false); + if (hMenu != IntPtr.Zero) + { + NativeMethods.EnableMenuItem(hMenu, (uint)NativeMethods.SC_RESTORE, NativeMethods.MF_BYCOMMAND | NativeMethods.MF_GRAYED); + NativeMethods.EnableMenuItem(hMenu, (uint)NativeMethods.SC_MINIMIZE, NativeMethods.MF_BYCOMMAND | NativeMethods.MF_GRAYED); + NativeMethods.EnableMenuItem(hMenu, (uint)NativeMethods.SC_MAXIMIZE, NativeMethods.MF_BYCOMMAND | NativeMethods.MF_GRAYED); + NativeMethods.EnableMenuItem(hMenu, (uint)NativeMethods.SC_CLOSE, NativeMethods.MF_BYCOMMAND | NativeMethods.MF_ENABLED); + + uint sizeMoveFlag = IsLocked ? NativeMethods.MF_GRAYED : NativeMethods.MF_ENABLED; + NativeMethods.EnableMenuItem(hMenu, (uint)NativeMethods.SC_MOVE, NativeMethods.MF_BYCOMMAND | sizeMoveFlag); + NativeMethods.EnableMenuItem(hMenu, (uint)NativeMethods.SC_SIZE, NativeMethods.MF_BYCOMMAND | sizeMoveFlag); + + Point anchorPt; + try + { + anchorPt = (StartButton != null && StartButton.IsVisible) + ? StartButton.PointToScreen(new Point(0, 0)) + : PointToScreen(new Point(0, 0)); + } + catch + { + anchorPt = new Point(Left * DpiScale, Top * DpiScale); + } + + int x = (int)anchorPt.X; + int y = (int)anchorPt.Y; + NativeMethods.TPM alignFlags = NativeMethods.TPM.LEFTALIGN; + + switch (AppBarEdge) + { + case AppBarEdge.Bottom: + alignFlags = NativeMethods.TPM.LEFTALIGN | NativeMethods.TPM.BOTTOMALIGN; + break; + case AppBarEdge.Top: + alignFlags = NativeMethods.TPM.LEFTALIGN | NativeMethods.TPM.TOPALIGN; + y += (int)((StartButton?.ActualHeight ?? ActualHeight) * DpiScale); + break; + case AppBarEdge.Left: + alignFlags = NativeMethods.TPM.LEFTALIGN | NativeMethods.TPM.TOPALIGN; + x += (int)((StartButton?.ActualWidth ?? ActualWidth) * DpiScale); + break; + case AppBarEdge.Right: + alignFlags = NativeMethods.TPM.RIGHTALIGN | NativeMethods.TPM.TOPALIGN; + break; + } + + uint cmd = NativeMethods.TrackPopupMenuEx(hMenu, NativeMethods.TPM.RETURNCMD | NativeMethods.TPM.LEFTBUTTON | NativeMethods.TPM.RIGHTBUTTON | NativeMethods.TPM.VERTICAL | alignFlags, x, y, hwnd, IntPtr.Zero); + if (cmd > 0) + { + IntPtr lParam = (cmd == (int)NativeMethods.SC_MOVE || cmd == (int)NativeMethods.SC_SIZE) + ? (IntPtr)NativeMethods.MakeLParam(x, y) + : IntPtr.Zero; + NativeMethods.SendMessage(hwnd, (int)NativeMethods.WM.SYSCOMMAND, (IntPtr)cmd, lParam); + } + } + + NativeMethods.SetWindowLong(hwnd, NativeMethods.WindowLongFlags.GWL_STYLE, oldStyle); + } + #endregion } } \ No newline at end of file diff --git a/RetroBar/Utilities/WindowManager.cs b/RetroBar/Utilities/WindowManager.cs index f27e3d58..c93e14f3 100644 --- a/RetroBar/Utilities/WindowManager.cs +++ b/RetroBar/Utilities/WindowManager.cs @@ -66,6 +66,27 @@ public void ReopenTaskbars() } } + public bool IsDraggingOrResizing { get; private set; } + + public void NotifyDragBegin() + { + IsDraggingOrResizing = true; + foreach (var taskbar in _taskbars) + { + taskbar.DeferWorkArea = true; + } + } + + public void NotifyDragEnd() + { + IsDraggingOrResizing = false; + foreach (var taskbar in _taskbars) + { + taskbar.DeferWorkArea = false; + taskbar.UpdatePosition(); + } + } + public void NotifyWorkAreaChange() { ShellLogger.Debug($"WindowManager: Work area change notification received"); From d972100023baf1d9c8a90c2833de15f296db50e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Thu, 13 Aug 2026 20:49:06 -0500 Subject: [PATCH 3/4] Address taskbar drag end handling comments --- RetroBar/Taskbar.xaml.cs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/RetroBar/Taskbar.xaml.cs b/RetroBar/Taskbar.xaml.cs index 8c8c86d6..1e3a1f7d 100644 --- a/RetroBar/Taskbar.xaml.cs +++ b/RetroBar/Taskbar.xaml.cs @@ -296,13 +296,13 @@ protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lP } else if (msg == (int)NativeMethods.WM.EXITSIZEMOVE) { - if (NativeMethods.GetAsyncKeyState((int)System.Windows.Forms.Keys.Escape) != 0) + if (NativeMethods.GetAsyncKeyState((int)System.Windows.Forms.Keys.Escape) < 0) { CancelDragOrResize(); } else { - windowManager?.NotifyDragEnd(); + EndDragOrResize(); } } else if (msg == (int)NativeMethods.WM.MOVING) @@ -318,7 +318,7 @@ protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lP } var desiredRect = GetDesiredRect(); - Marshal.StructureToPtr(desiredRect, lParam, true); + Marshal.StructureToPtr(desiredRect, lParam, false); return (IntPtr)1; } else if (msg == (int)NativeMethods.WM.SIZING) @@ -334,7 +334,7 @@ protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lP } var desiredRect = GetDesiredRect(); - Marshal.StructureToPtr(desiredRect, lParam, true); + Marshal.StructureToPtr(desiredRect, lParam, false); return (IntPtr)1; } @@ -659,22 +659,17 @@ private void BeginDragOrResize() private void EndDragOrResize() { - bool wasActive = _isDragging || _mouseDragResize; _isDragging = false; _mouseDragResize = false; _mouseDragStart = null; Cursor = Cursors.Arrow; ReleaseMouseCapture(); - if (wasActive) - { - windowManager?.NotifyDragEnd(); - } + windowManager?.NotifyDragEnd(); } private void CancelDragOrResize() { - bool wasActive = _isDragging || _mouseDragResize; _isDragging = false; _mouseDragResize = false; _mouseDragStart = null; @@ -694,10 +689,7 @@ private void CancelDragOrResize() Settings.Instance.TaskbarWidth = _dragStartTaskbarWidth; } - if (wasActive) - { - windowManager?.NotifyDragEnd(); - } + windowManager?.NotifyDragEnd(); } private void Taskbar_PreviewKeyDown(object sender, KeyEventArgs e) From bc89d5bc19d1d8183d5bf08664fb3287b7b70f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Sat, 15 Aug 2026 09:41:42 -0500 Subject: [PATCH 4/4] Fix system menu resizing, simplify drag logic --- RetroBar/Taskbar.xaml.cs | 107 ++++++++++++--------------------------- 1 file changed, 31 insertions(+), 76 deletions(-) diff --git a/RetroBar/Taskbar.xaml.cs b/RetroBar/Taskbar.xaml.cs index c7d13b23..7d3a535d 100644 --- a/RetroBar/Taskbar.xaml.cs +++ b/RetroBar/Taskbar.xaml.cs @@ -61,6 +61,7 @@ public Taskbar(WindowManager windowManager, DictionaryManager dictionaryManager, this.hotkeyManager = hotkeyManager; InitializeComponent(); + SetLayoutRounding(); DataContext = _shellManager; StartButton.StartMenuMonitor = startMenuMonitor; @@ -532,6 +533,10 @@ private void RecalculateSize(bool performResize = true) if (!performResize) { + if (heightChanged || widthChanged) + { + UpdatePosition(); + } return; } @@ -752,7 +757,7 @@ private void ProcessResize(int coordinate) double baseHeight = (Settings.Instance.TaskbarScale * (Application.Current.FindResource("TaskbarHeight") as double? ?? 0)) * DpiScale; if (AppBarMode == AppBarMode.AutoHide || !Settings.Instance.LockTaskbar) { - baseHeight += _unlockedMargin; + baseHeight += _unlockedMargin * DpiScale; } int targetRows = 1 + (int)Math.Max(0, Math.Round((distance - baseHeight) / scaledRowHeight)); @@ -771,7 +776,7 @@ private void ProcessResize(int coordinate) double baseWidth = (Settings.Instance.TaskbarScale * (Application.Current.FindResource("TaskbarWidth") as double? ?? 0)) * DpiScale; if (AppBarMode == AppBarMode.AutoHide || !Settings.Instance.LockTaskbar) { - baseWidth += _unlockedMargin; + baseWidth += _unlockedMargin * DpiScale; } int targetWidth = 1 + (int)Math.Max(0, Math.Round((distance - baseWidth) / scaledRowHeight)); @@ -797,92 +802,42 @@ private void Taskbar_OnMouseMove(object sender, MouseEventArgs e) if (_mouseDragResize) { - Point cursorPosition = PointToScreen(Mouse.GetPosition(this)); - int currentCoord = Orientation == Orientation.Horizontal ? (int)cursorPosition.Y : (int)cursorPosition.X; - ProcessResize(currentCoord); + if (NativeMethods.GetCursorPos(out NativeMethods.POINT pt)) + { + int currentCoord = Orientation == Orientation.Horizontal ? pt.y : pt.x; + ProcessResize(currentCoord); + } return; } // reposition‐while‐dragging - if (!_isDragging || _mouseDragStart == null) + if (!_isDragging) return; - // only start moving edge after system drag threshold - var screenPos = PointToScreen(e.GetPosition(this)); - if (Math.Abs(screenPos.X - _mouseDragStart.Value.X) <= SystemParameters.MinimumHorizontalDragDistance && - Math.Abs(screenPos.Y - _mouseDragStart.Value.Y) <= SystemParameters.MinimumVerticalDragDistance) - return; - - var newEdge = DragCoordsToScreenEdge((int)screenPos.X, (int)screenPos.Y); - - if (newEdge != AppBarEdge) - Settings.Instance.Edge = newEdge; + if (NativeMethods.GetCursorPos(out NativeMethods.POINT cursorPt)) + { + var newEdge = DragCoordsToScreenEdge(cursorPt.x, cursorPt.y); + if (newEdge != AppBarEdge) + { + Settings.Instance.Edge = newEdge; + } + } } private AppBarEdge DragCoordsToScreenEdge(int x, int y) { - // The areas of the screen which determine the dragged-to edge are divided in an X. - // To determine the edge, split the screen into quadrants, and then split the quadrants diagonally, alternating. - double relativeX = ((double)x - Screen.Bounds.Left) / Screen.Bounds.Width; - double relativeY = ((double)y - Screen.Bounds.Top) / Screen.Bounds.Height; + double relX = (double)x - Screen.Bounds.Left; + double relY = (double)y - Screen.Bounds.Top; + double width = Screen.Bounds.Width; + double height = Screen.Bounds.Height; - // We will use the relative coordinates to form quadrants - // Determine the edge based on the quadrant + AppBarEdge vertEdge = relX < width / 2 ? AppBarEdge.Left : AppBarEdge.Right; + double errorX = relX < width / 2 ? relX : width - relX; - if (relativeX < 0.5 && relativeY < 0.5) - { - // top-left quadrant - if (relativeX >= relativeY) - { - return AppBarEdge.Top; - } - else - { - return AppBarEdge.Left; - } - } - else if (relativeX >= 0.5 && relativeY < 0.5) - { - // top-right quadrant - // adjust relativeX to the same base as relativeY - relativeX -= 0.5; + AppBarEdge horzEdge = relY < height / 2 ? AppBarEdge.Top : AppBarEdge.Bottom; + double errorY = relY < height / 2 ? relY : height - relY; - if (relativeX + relativeY < 0.5) - { - return AppBarEdge.Top; - } - else - { - return AppBarEdge.Right; - } - } - else if (relativeX < 0.5 && relativeY >= 0.5) - { - // bottom-left quadrant - // adjust relativeY to the same base as relativeX - relativeY -= 0.5; - - if (relativeX + relativeY < 0.5) - { - return AppBarEdge.Left; - } - else - { - return AppBarEdge.Bottom; - } - } - else - { - // bottom-right quadrant - if (relativeX >= relativeY) - { - return AppBarEdge.Right; - } - else - { - return AppBarEdge.Bottom; - } - } + return (errorY * width > errorX * height) ? vertEdge : horzEdge; } private bool IsMouseInResizeArea() @@ -966,7 +921,7 @@ private void ShowSystemMenu(IntPtr hwnd) IntPtr lParam = (cmd == (int)NativeMethods.SC_MOVE || cmd == (int)NativeMethods.SC_SIZE) ? (IntPtr)NativeMethods.MakeLParam(x, y) : IntPtr.Zero; - NativeMethods.SendMessage(hwnd, (int)NativeMethods.WM.SYSCOMMAND, (IntPtr)cmd, lParam); + NativeMethods.PostMessage(hwnd, (uint)NativeMethods.WM.SYSCOMMAND, (IntPtr)cmd, lParam); } }