Skip to content

feat(grabandmove): snap windows to screen edges and corners while dragging - #50245

Closed
James Martin (jjamesmartiin) wants to merge 1 commit into
microsoft:mainfrom
jjamesmartiin:feat/win-click-edge-snap
Closed

James Martin (jjamesmartiin) wants to merge 1 commit into
microsoft:mainfrom
jjamesmartiin:feat/win-click-edge-snap

Conversation

@jjamesmartiin

@jjamesmartiin James Martin (jjamesmartiin) commented Aug 29, 2026 •

Copy link
Copy Markdown

Summary of the Pull Request

Adds release-to-edge window snapping to the GrabAndMove utility. When dragging windows using the modifier key (Alt/Win + Left Click), dragging near the left or right work-area edge previews and snaps the window to that screen half using direct window positioning. Dragging to a screen corner previews and snaps the window to that quarter, and dragging to the top work-area edge previews and maximizes the window.

PR Checklist

Detailed Description of the Pull Request / Additional comments

Technical Implementation Details:

  1. Edge Snapping Logic (src/modules/GrabAndMove/GrabAndMove/main.cpp):
    • Added ComputeSnapTarget() to calculate work-area boundaries (GetWorkAreaForPoint) and determine if the cursor is within SNAP_EDGE_THRESHOLD (24px) of an edge or corner. Corners are checked first so quadrants take priority over the half-screen/maximize edge zones.
    • When armed, the existing DWM overlay window (RepositionOverlay) expands to preview the half-screen, quarter-screen, or full-screen target region with zeroed margins.
    • On mouse release, if armed:
      • Half / Corner: Positions the dragged window directly via SetWindowPos() so its visible frame (DWM extended frame bounds) matches the previewed half/quarter region, accounting for the invisible resize borders.
      • Top Edge: Calls ShowWindow(g_dragTarget, SW_MAXIMIZE).
  2. Settings UI Integration:
    • Added SnapToEdges (snapToEdges bool property, default true) to GrabAndMoveProperties.cs.
    • Added ViewModel binding in GrabAndMoveViewModel.cs and UI toggle switch in GrabAndMovePage.xaml.
    • Updated Resources.resw (en-us).

Validation Steps Performed

  1. Standalone GrabAndMove Execution:
    • Executed PowerToys.GrabAndMove.exe with explorer PID runner parameter.
  2. Window Movement & Snapping:
    • Held Alt + left-click drag on target windows (e.g. Notepad, Edge, Command Prompt).
    • Dragged cursor to left work-area edge -> Gold preview overlay rendered on left half of monitor -> Released mouse -> Window snapped to left half.
    • Dragged cursor to right work-area edge -> Overlay rendered on right half -> Released mouse -> Window snapped to right half.
    • Dragged cursor to top work-area edge -> Overlay rendered on entire work area -> Released mouse -> Window maximized.
    • Dragged cursor to each screen corner -> Overlay rendered on that quarter -> Released mouse -> Window snapped to that quarter.
  3. Settings UI Validation:
    • Toggled "Snap windows to screen edges while dragging" off -> Dragging to edges showed standard window outline without triggering snap.
    • Toggled on -> Edge snap functionality restored.
  4. Regression Check:
    • Confirmed Alt + Right-Click window resize operations function normally without interference.

Video Demonstration

power-toys-grab-and-move-snapping-demo-10mb.mp4
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.
@github-actions github-actions Bot added Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something Product-Grab And Move labels Aug 29, 2026
@jjamesmartiin

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@microsoft-github-policy-service microsoft-github-policy-service Bot added Needs-Triage For issues raised to be triaged and prioritized by internal Microsoft teams Needs-Team-Response An issue author responded so the team needs to follow up and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Aug 29, 2026
@jjamesmartiin

James Martin (jjamesmartiin) commented Aug 29, 2026 •

Copy link
Copy Markdown
Author

📷 Visual Evidence & UI Summary

As requested by the PR intake bot check, here is the visual breakdown for the UI additions in GrabAndMovePage.xaml:

1. Settings UI Integration (GrabAndMovePage.xaml & Resources.resw)

  • Toggle Switch: Added Snap windows to screen edges while dragging (SnapToEdges, default true).
  • Header: Snap windows to screen edges while dragging
  • Description: Release a dragged window near the left or right edge to snap it to that half, or near the top edge to maximize it
image

2. Edge Snapping Overlay Behavior

  • Left / Right Edge: Dragging within 16px of the left or right work-area edge displays the DWM target overlay across that half of the screen; releasing sends synthetic Win + Left / Win + Right to trigger native Aero Snap.
  • Top Edge: Dragging near the top work-area edge displays a full work-area overlay; releasing calls ShowWindow(..., SW_MAXIMIZE).

1/4 Snap

image

1/2 Snap

image

Full-screen

image
@github-actions github-actions Bot added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Aug 30, 2026
@jjamesmartiin
James Martin (jjamesmartiin) force-pushed the feat/win-click-edge-snap branch 2 times, most recently from 699a952 to d835ec5 Compare August 30, 2026 01:46
@jjamesmartiin

James Martin (jjamesmartiin) commented Aug 30, 2026 •

Copy link
Copy Markdown
Author

Hi! I've put together a short (~30 second) screen recording that walks through the snapping behavior end-to-end: the new Settings toggle, snapping to a half at the left/right edge, snapping to a quarter at the corners, and maximizing at the top edge. Please let me know if there's anything else you'd like me to include or if you have any feedback on the behavior or the settings wording — happy to make changes. Thanks for taking a look!

power-toys-grab-and-move-snapping-demo-10mb.mp4
@microsoft-github-policy-service microsoft-github-policy-service Bot removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Aug 30, 2026
@jjamesmartiin James Martin (jjamesmartiin) changed the title feat(grabandmove): snap windows to screen edges while dragging Aug 30, 2026
@jjamesmartiin

Copy link
Copy Markdown
Author

Quick note: I ran clang-format against main.cpp (per the repo's .clang-format) to make sure the new edge/corner snapping code matches the style guide. Purely formatting — no behavior changes — and Debug + Release builds are both clean.

@MuyuanMS Muyuan Li (MuyuanMS) self-assigned this Sep 1, 2026
…gging

Add release-to-edge-snap to GrabAndMove:
- Arm edge-snap when dragging near the left/right/top work-area edge or a
  screen corner
- Reuse the existing overlay to preview the target half/quarter/maximize region
- On release: position the window directly into the half/quarter region
  (accounting for DWM frame bounds), or maximize at the top edge
- Add snapToEdges setting (default on) with Settings UI toggle + localization
@jiripolasek

Copy link
Copy Markdown
Collaborator
  • What about Fancy Zones? :'(
  • I don't think this PR should be used to mark 37124 as closed, the relation is weak.
@jjamesmartiin

James Martin (jjamesmartiin) commented Sep 2, 2026 •

Copy link
Copy Markdown
Author
  • What about Fancy Zones? :'(

Oh good point! I can try to get that added in. I also noticed that when you grab the window it doesn't bring the window to the front. Would this sort of behavior be OK to add as a toggle? (maybe some people are used to how it is now and would prefer to be able to keep it off?)

@jjamesmartiin

James Martin (jjamesmartiin) commented Sep 2, 2026 •

Copy link
Copy Markdown
Author
  • I don't think this PR should be used to mark 37124 as closed, the relation is weak.

This should be a closer related issue, not sure why it didn't appear in my first search.

#47598

@jjamesmartiin

Copy link
Copy Markdown
Author
  • What about Fancy Zones? :'(

Oh good point! I can try to get that added in. I also noticed that when you grab the window it doesn't bring the window to the front. Would this sort of behavior be OK to add as a toggle? (maybe some people are used to how it is now and would prefer to be able to keep it off?)

I also noticed it's not going to work on the snap assist, so I'll try to get it working too.

@jjamesmartiin

Copy link
Copy Markdown
Author

Muyuan Li (@MuyuanMS) should I move this back to ready to merge and work on the FancyZones and Snap Assist in a separate PR?

@jjamesmartiin
James Martin (jjamesmartiin) marked this pull request as ready for review September 4, 2026 22:08
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.
@jjamesmartiin

Copy link
Copy Markdown
Author

I've marked this PR as ready for review. I decided to scope out the remaining features—such as FancyZones integration, Snap Assistant support, and bringing windows to the front—which I'm still working on. I'll submit those in separate follow-up PRs once they are ready.

@sdurni-glitch

This comment was marked as spam.

@MuyuanMS

Copy link
Copy Markdown
Contributor

Keep snap work off the low-level hook and preserve destination-monitor maximize/restore bounds

Severity: high

Affected code:

  • src/modules/GrabAndMove/GrabAndMove/GrabAndMove.cpp — GrabAndMove::OnLowLevelMouseHook
  • src/modules/GrabAndMove/GrabAndMove/GrabAndMove.cpp — GrabAndMove::HandleMouseButtonUp
  • src/modules/GrabAndMove/GrabAndMove/GrabAndMoveWindowUtils.cpp — GetTargetMonitorRect / RestoreMaximizedWindow

Problem: Release-time snap placement still runs in the WH_MOUSE_LL callback and the maximize path overwrites the selected destination monitor with the current monitor state. That makes cross-monitor drags and restore-after-maximize vulnerable to stale rectangles or taskbar-offset jumps.

Why it matters: A low-level hook is the wrong place for the final window move and maximize work because the callback is latency-sensitive and can be interrupted by the target app or a monitor transition. If the selected destination monitor is dropped, the final snap lands on the wrong monitor and restoring the maximized window can jump to the wrong workspace bounds.

Suggested change:

  1. Queue the release-time snap request to the GrabAndMove message window and carry the release-time rectangle through the queued payload instead of doing the move directly from the low-level hook.
  2. In the queued handler, select the destination monitor from the release-time target rect before calling maximize, and preserve the window's original normal bounds for restore instead of overwriting them with the source-monitor placement data.
  3. Validate the restore path on a multi-monitor drag by confirming the window snaps to the chosen monitor and the maximized window restores to the preserved bounds rather than the stale taskbar-offset rectangle.

Verification: Run the GrabAndMove Debug build and exercise a cross-monitor edge/corner drag followed by maximize and restore on a multi-monitor setup; confirm the final window stays on the chosen monitor and restore returns to the correct bounds without a stale jump.

@MuyuanMS

Copy link
Copy Markdown
Contributor

Recompute final snap bounds after the monitor/DPI transition and bound delayed focus restore

Severity: medium

Affected code:

  • src/modules/GrabAndMove/GrabAndMove/GrabAndMove.cpp — GrabAndMove::TrySnapWindow
  • src/modules/GrabAndMove/GrabAndMove/GrabAndMove.cpp — GrabAndMove::MaximizeWindowAsync
  • src/modules/GrabAndMove/GrabAndMove/GrabAndMoveWindowUtils.cpp — ComputeSnapBoundsForMonitor / RestoreForegroundWindow

Problem: The snap logic calculates the final side/corner bounds from the source monitor before the async monitor/DPI transition completes, and delayed focus restoration is not bounded to the active maximize target. That can leave a stale retry loop or restore the wrong foreground window after a drag that crosses monitors or changes DPI.

Why it matters: A source-monitor margin calculation can be stale once the window moves to its destination monitor, and a non-bounded focus retry can keep a timer alive after the target has changed or hung. Both issues create visible drift or focus theft during edge/corner snap completion.

Suggested change:

  1. Recompute the final side/corner bounds after the window reaches the destination monitor and the monitor/DPI transition has settled, rather than trusting the source-monitor margin values.
  2. Keep the delayed focus restore state bounded to the actual maximize target and clear it when the target no longer matches the active foreground window or the retry budget is exhausted.
  3. Validate the flow with a monitor-DPI transition and a follow-up maximize/restore to confirm the final bounds and focus restoration stay aligned with the target window.

Verification: Run the GrabAndMove debug validation on a monitor/DPI-change scenario and confirm the final snap lands at the correct edge/corner and focus restoration stays bounded to the active target without a stale retry timer.

@MuyuanMS

Copy link
Copy Markdown
Contributor

Close PR due to long time inactivity - please reopen once ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs-Team-Response An issue author responded so the team needs to follow up Needs-Triage For issues raised to be triaged and prioritized by internal Microsoft teams Product-Grab And Move Ready for review

4 participants