https://git.reactos.org/?p=reactos.git;a=commitdiff;h=f3d03760e9202c18ce2eb…
commit f3d03760e9202c18ce2ebc115ffb539f39fc2e3d
Author: Whindmar Saksit <whindsaks(a)proton.me>
AuthorDate: Sun Aug 11 20:21:58 2024 +0200
Commit: GitHub <noreply(a)github.com>
CommitDate: Sun Aug 11 20:21:58 2024 +0200
[NTUSER] Rewrite Window Snap handling (#5705)
Fixes many Window Snap related bugs and uses the WS_EX2_VERTICALLYMAXIMIZED* styles to
remember which edge it is snapped to.
The most significant change is that GetWindowPlacement lies about the normal position
when it is snapped, just like Windows.
CORE-19160 CORE-19165 CORE-19166
---
win32ss/user/ntuser/defwnd.c | 110 +++++----------
win32ss/user/ntuser/nonclient.c | 288 ++++++++++++++++++++++------------------
win32ss/user/ntuser/winpos.c | 150 ++++++++++++++++++++-
win32ss/user/ntuser/winpos.h | 31 +++++
4 files changed, 370 insertions(+), 209 deletions(-)
diff --git a/win32ss/user/ntuser/defwnd.c b/win32ss/user/ntuser/defwnd.c
index 33911c5ea5b..368105afc42 100644
--- a/win32ss/user/ntuser/defwnd.c
+++ b/win32ss/user/ntuser/defwnd.c
@@ -788,112 +788,66 @@ IntDefWindowProc(
}
if (g_bWindowSnapEnabled && (IS_KEY_DOWN(gafAsyncKeyState, VK_LWIN) ||
IS_KEY_DOWN(gafAsyncKeyState, VK_RWIN)))
{
- BOOL IsTaskBar;
- DWORD StyleTB;
- DWORD ExStyleTB;
HWND hwndTop = UserGetForegroundWindow();
PWND topWnd = UserGetWindowObject(hwndTop);
+ BOOL allowSnap;
// MS Doc: foreground window can be NULL, e.g. when window is losing
activation
if (!topWnd)
return 0;
- // We want to forbid snapping operations on the TaskBar
- // We use a heuristic for detecting the TaskBar Wnd by its typical Style
& ExStyle Values
- ExStyleTB = (topWnd->ExStyle & WS_EX_TOOLWINDOW);
- StyleTB = (topWnd->style & (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN));
- IsTaskBar = (StyleTB == (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN))
- && (ExStyleTB == WS_EX_TOOLWINDOW);
- TRACE("ExStyle=%x Style=%x IsTaskBar=%d\n", ExStyleTB, StyleTB,
IsTaskBar);
+ allowSnap = IntIsSnapAllowedForWindow(topWnd);
+ /* Allow the minimize action if it has a minimize button, even if the window
cannot be snapped (e.g. Calc.exe) */
+ if (!allowSnap && (topWnd->style &
(WS_MINIMIZEBOX|WS_THICKFRAME)) == WS_MINIMIZEBOX)
+ allowSnap = wParam == VK_DOWN;
- if (!IsTaskBar)
+ if (allowSnap)
{
- if ((topWnd->style & WS_THICKFRAME) == 0)
- return 0;
+ UINT snapped = IntGetWindowSnapEdge(topWnd);
if (wParam == VK_DOWN)
{
if (topWnd->style & WS_MAXIMIZE)
- {
- co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_RESTORE, lParam);
-
- /* "Normal size" must be erased after restoring,
otherwise it will block next side snap actions */
- RECTL_vSetEmptyRect(&topWnd->InternalPos.NormalRect);
- }
+ co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_RESTORE, MAKELONG(0,
1));
+ else if (snapped)
+ co_IntUnsnapWindow(topWnd);
else
- {
- co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_MINIMIZE, lParam);
- }
+ co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_MINIMIZE, MAKELONG(0,
1));
}
else if (wParam == VK_UP)
{
- RECT currentRect;
- if ((topWnd->InternalPos.NormalRect.right ==
topWnd->InternalPos.NormalRect.left) ||
- (topWnd->InternalPos.NormalRect.top ==
topWnd->InternalPos.NormalRect.bottom))
- {
- currentRect = topWnd->rcWindow;
- }
- else
- {
- currentRect = topWnd->InternalPos.NormalRect;
- }
- co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
-
- // save normal rect if maximazing snapped window
- topWnd->InternalPos.NormalRect = currentRect;
+ if (topWnd->style & WS_MINIMIZE)
+ co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_RESTORE, MAKELONG(0,
1));
+ else
+ co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_MAXIMIZE, MAKELONG(0,
1));
}
else if (wParam == VK_LEFT || wParam == VK_RIGHT)
{
- RECT snapRect, normalRect, windowRect;
- BOOL snapped;
- normalRect = topWnd->InternalPos.NormalRect;
- snapped = (normalRect.left != 0 && normalRect.right != 0
&&
- normalRect.top != 0 && normalRect.bottom != 0);
+ UINT edge = wParam == VK_LEFT ? HTLEFT : HTRIGHT;
+ UINT otherEdge = edge == HTLEFT ? HTRIGHT : HTLEFT;
if (topWnd->style & WS_MAXIMIZE)
{
- co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_RESTORE, lParam);
- snapped = FALSE;
- }
- windowRect = topWnd->rcWindow;
-
- UserSystemParametersInfo(SPI_GETWORKAREA, 0, &snapRect, 0);
- if (wParam == VK_LEFT)
- {
- snapRect.right = (snapRect.left + snapRect.right) / 2;
+ /* SC_RESTORE + Snap causes the window to visually move twice, place
it manually in the snap position */
+ RECT normalRect = topWnd->InternalPos.NormalRect;
+ co_IntCalculateSnapPosition(topWnd, edge,
&topWnd->InternalPos.NormalRect); /* Calculate edge position */
+ IntSetSnapEdge(topWnd, edge); /* Tell everyone the edge we are
snapped to */
+ co_IntSendMessage(hwndTop, WM_SYSCOMMAND, SC_RESTORE, MAKELONG(0,
1));
+ IntSetSnapInfo(topWnd, edge, &normalRect); /* Reset the real
place to unsnap to */
+ snapped = HTNOWHERE; /* Force snap */
}
- else // VK_RIGHT
+#if 0 /* Windows 8 does this but is it a good feature? */
+ else if (snapped == edge)
{
- snapRect.left = (snapRect.left + snapRect.right) / 2;
+ /* Already snapped to this edge, snap to the opposite side */
+ edge = otherEdge;
}
+#endif
- if (snapped)
- {
- // if window was snapped but moved to other location - restore
normal size
- if (!IntEqualRect(&snapRect, &windowRect))
- {
- RECT empty = {0, 0, 0, 0};
- co_WinPosSetWindowPos(topWnd,
- 0,
- normalRect.left,
- normalRect.top,
- normalRect.right - normalRect.left,
- normalRect.bottom - normalRect.top,
- 0);
- topWnd->InternalPos.NormalRect = empty;
- }
- }
+ if (snapped == otherEdge)
+ co_IntUnsnapWindow(topWnd);
else
- {
- co_WinPosSetWindowPos(topWnd,
- 0,
- snapRect.left,
- snapRect.top,
- snapRect.right - snapRect.left,
- snapRect.bottom - snapRect.top,
- 0);
- topWnd->InternalPos.NormalRect = windowRect;
- }
+ co_IntSnapWindow(topWnd, edge);
}
}
}
diff --git a/win32ss/user/ntuser/nonclient.c b/win32ss/user/ntuser/nonclient.c
index 2324cc04afd..f3a0cdb8492 100644
--- a/win32ss/user/ntuser/nonclient.c
+++ b/win32ss/user/ntuser/nonclient.c
@@ -136,6 +136,18 @@ NC_GetSysPopupPos(PWND Wnd, RECT *Rect)
}
}
+static UINT
+GetSnapActivationPoint(PWND Wnd, POINT pt)
+{
+ RECT wa;
+ UserSystemParametersInfo(SPI_GETWORKAREA, 0, &wa, 0); /* FIXME: MultiMon of PWND
*/
+
+ if (pt.x <= wa.left) return HTLEFT;
+ if (pt.x >= wa.right-1) return HTRIGHT;
+ if (pt.y <= wa.top) return HTTOP; /* Maximize */
+ return HTNOWHERE;
+}
+
LONG FASTCALL
DefWndStartSizeMove(PWND Wnd, WPARAM wParam, POINT *capturePoint)
{
@@ -239,13 +251,15 @@ VOID FASTCALL
DefWndDoSizeMove(PWND pwnd, WORD wParam)
{
MSG msg;
- RECT sizingRect, mouseRect, origRect, unmodRect;
+ RECT sizingRect, mouseRect, origRect, unmodRect, snapPreviewRect;
+ PRECT pFrameRect = &sizingRect;
HDC hdc;
LONG hittest = (LONG)(wParam & 0x0f);
PCURICON_OBJECT DragCursor = NULL, OldCursor = NULL;
POINT minTrack, maxTrack;
POINT capturePoint, pt;
ULONG Style, ExStyle;
+ UINT orgSnap = IntGetWindowSnapEdge(pwnd), snap = orgSnap;
BOOL thickframe;
BOOL iconic;
BOOL moved = FALSE;
@@ -262,6 +276,8 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
iconic = (Style & WS_MINIMIZE) != 0;
if (((Style & WS_MAXIMIZE) && syscommand != SC_MOVE) ||
!IntIsWindowVisible(pwnd)) return;
+ if ((Style & (WS_MAXIMIZE | WS_CHILD)) == WS_MAXIMIZE)
+ orgSnap = snap = HTTOP;
thickframe = UserHasThickFrameStyle(Style, ExStyle) && !iconic;
@@ -294,7 +310,7 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
{
co_UserSetCapture(UserHMGetHandle(pwnd));
hittest = DefWndStartSizeMove(pwnd, wParam, &capturePoint);
- if (!hittest)
+ if (!hittest)
{
IntReleaseCapture();
return;
@@ -402,56 +418,20 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
else if (g_bWindowSnapEnabled && (msg.message == WM_LBUTTONUP ||
(msg.message == WM_MOUSEMOVE && (msg.wParam & MK_LBUTTON) ==
0)))
{ // If WindowSnapEnabled: Decide whether to snap before exiting
- DWORD ExStyleTB, StyleTB;
- BOOL IsTaskBar;
-
- // We want to forbid snapping operations on the TaskBar
- // We use a heuristic for detecting the TaskBar Wnd by its typical Style &
ExStyle Values
- ExStyleTB = (ExStyle & WS_EX_TOOLWINDOW);
- StyleTB = (Style & (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN));
- IsTaskBar = (StyleTB == (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN))
- && (ExStyleTB == WS_EX_TOOLWINDOW);
- TRACE("ExStyle=%x Style=%x IsTaskBar=%d\n", ExStyleTB, StyleTB,
IsTaskBar);
-
- // check for snapping if was moved by caption
- if (!IsTaskBar && hittest == HTCAPTION && thickframe &&
(ExStyle & WS_EX_MDICHILD) == 0)
+ if (hittest == HTCAPTION && thickframe && /* Check for snapping
if was moved by caption */
+ IntIsSnapAllowedForWindow(pwnd) && (ExStyle & WS_EX_MDICHILD) ==
0)
{
- RECT snapRect;
- BOOL doSideSnap = FALSE;
- UserSystemParametersInfo(SPI_GETWORKAREA, 0, &snapRect, 0);
-
- // snap to left
- if (pt.x <= snapRect.left)
- {
- snapRect.right = (snapRect.right - snapRect.left) / 2 + snapRect.left;
- doSideSnap = TRUE;
- }
- // snap to right
- if (pt.x >= snapRect.right-1)
+ BOOLEAN wasSnap = IntIsWindowSnapped(pwnd); /* Need the live snap state, not
orgSnap nor maximized state */
+ UINT snapTo = iconic ? HTNOWHERE : GetSnapActivationPoint(pwnd, pt);
+ if (snapTo)
{
- snapRect.left = (snapRect.right - snapRect.left) / 2 + snapRect.left;
- doSideSnap = TRUE;
- }
-
- if (doSideSnap)
- {
- co_WinPosSetWindowPos(pwnd,
- NULL,
- snapRect.left,
- snapRect.top,
- snapRect.right - snapRect.left,
- snapRect.bottom - snapRect.top,
- SWP_NOACTIVATE);
- pwnd->InternalPos.NormalRect = origRect;
- }
- else
- {
- // maximize if on dragged to top
- if (pt.y <= snapRect.top)
- {
- co_IntSendMessage(UserHMGetHandle(pwnd), WM_SYSCOMMAND, SC_MAXIMIZE,
0);
- pwnd->InternalPos.NormalRect = origRect;
- }
+ if (DragFullWindows)
+ {
+ co_IntSnapWindow(pwnd, snapTo);
+ if (!wasSnap)
+ pwnd->InternalPos.NormalRect = origRect;
+ }
+ snap = snapTo;
}
}
break;
@@ -468,10 +448,10 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
if (msg.message == WM_KEYDOWN) switch(msg.wParam)
{
- case VK_UP: pt.y -= 8; break;
- case VK_DOWN: pt.y += 8; break;
- case VK_LEFT: pt.x -= 8; break;
- case VK_RIGHT: pt.x += 8; break;
+ case VK_UP: pt.y -= 8; break;
+ case VK_DOWN: pt.y += 8; break;
+ case VK_LEFT: pt.x -= 8; break;
+ case VK_RIGHT: pt.x += 8; break;
}
pt.x = max( pt.x, mouseRect.left );
@@ -484,63 +464,99 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
if (dx || dy)
{
- if ( !moved )
- {
- moved = TRUE;
- if ( iconic ) /* ok, no system popup tracking */
+ if (!moved)
+ {
+ moved = TRUE;
+ if (iconic) /* ok, no system popup tracking */
{
OldCursor = UserSetCursor(DragCursor, FALSE);
- UserShowCursor( TRUE );
+ UserShowCursor(TRUE);
}
- else if(!DragFullWindows)
- UserDrawMovingFrame( hdc, &sizingRect, thickframe );
- }
+ else if (!DragFullWindows)
+ UserDrawMovingFrame(hdc, &sizingRect, thickframe);
+ }
- if (msg.message == WM_KEYDOWN) UserSetCursorPos(pt.x, pt.y, 0, 0, FALSE);
- else
- {
- RECT newRect = unmodRect;
+ if (msg.message == WM_KEYDOWN)
+ {
+ UserSetCursorPos(pt.x, pt.y, 0, 0, FALSE);
+ }
+ else
+ {
+ RECT newRect = unmodRect;
- if (!iconic && !DragFullWindows) UserDrawMovingFrame( hdc,
&sizingRect, thickframe );
+ if (!iconic && !DragFullWindows)
+ {
+ UserDrawMovingFrame(hdc, pFrameRect, thickframe);
+ pFrameRect = &sizingRect;
+ }
if (hittest == HTCAPTION)
{
/* Restore window size if it is snapped */
- if (!RECTL_bIsEmptyRect(&pwnd->InternalPos.NormalRect) &&
- !IntEqualRect(&pwnd->InternalPos.NormalRect,
&pwnd->rcWindow))
+ PRECT pr = &newRect;
+ LONG width, height, capcy, snapTo;
+ if (snap && syscommand == SC_MOVE && !iconic &&
+ !RECTL_bIsEmptyRect(&pwnd->InternalPos.NormalRect))
{
- UserSetCursorPos(max(0, pwnd->InternalPos.NormalRect.left) + pt.x,
pwnd->InternalPos.NormalRect.top + pt.y, 0, 0, FALSE);
-
- /* Save normal size - it required when window unsnapped from one side
and snapped to another holding mouse down */
- origRect = pwnd->InternalPos.NormalRect;
-
- /* Restore from maximized state */
- if (Style & WS_MAXIMIZE)
+ *pr = pwnd->InternalPos.NormalRect;
+ origRect = *pr; /* Save normal size - is required when window unsnapped
from one side and snapped to another holding mouse down */
+
+ /* Try to position the center of the caption where the mouse is
horizontally */
+ capcy = UserGetSystemMetrics((ExStyle & WS_EX_TOPMOST) ?
SM_CYSMCAPTION : SM_CYCAPTION); /* No border, close enough */
+ width = pr->right - pr->left;
+ height = pr->bottom - pr->top;
+ pr->left = pt.x - width / 2;
+ pr->right = pr->left + width;
+ pr->top = mouseRect.top;
+ pr->bottom = pr->top + height;
+ if (pr->left < mouseRect.left)
{
- co_IntSendMessage(UserHMGetHandle(pwnd), WM_SYSCOMMAND, SC_RESTORE,
0);
+ pr->left = mouseRect.left;
+ pr->right = pr->left + width;
}
- /* Restore snapped to left/right place */
- else
+ if ((pwnd->ExStyle & WS_EX_LAYOUTRTL) && pr->right
> mouseRect.right)
{
- co_WinPosSetWindowPos(pwnd,
- NULL,
- pwnd->InternalPos.NormalRect.left,
- pwnd->InternalPos.NormalRect.top,
- pwnd->InternalPos.NormalRect.right -
pwnd->InternalPos.NormalRect.left,
- pwnd->InternalPos.NormalRect.bottom -
pwnd->InternalPos.NormalRect.top,
- 0);
+ pr->left = mouseRect.right - width;
+ pr->right = pr->left + width;
+ }
+ UserSetCursorPos(pt.x, pr->top + capcy / 2, 0, 0, FALSE);
+ snap = FALSE;
+ dx = dy = 0; /* Don't offset this move */
+ if (DragFullWindows)
+ {
+ IntSetStyle(pwnd, 0, WS_MAXIMIZE);
+ IntSetSnapEdge(pwnd, HTNOWHERE);
+
+ /* Have to move and size it now because we don't want
SWP_NOSIZE */
+ co_WinPosSetWindowPos(pwnd, HWND_TOP, pr->left, pr->top,
width, height, SWP_NOACTIVATE);
+ }
+ }
+ else if (!snap && syscommand == SC_MOVE && !iconic)
+ {
+ if ((snapTo = GetSnapActivationPoint(pwnd, pt)) != 0)
+ {
+ co_IntCalculateSnapPosition(pwnd, snapTo, &snapPreviewRect);
+ if (DragFullWindows)
+ {
+ /* TODO: Show preview of snap */
+ }
+ else
+ {
+ pFrameRect = &snapPreviewRect;
+ UserDrawMovingFrame(hdc, pFrameRect, thickframe);
+ continue;
+ }
}
- RECTL_vSetEmptyRect(&pwnd->InternalPos.NormalRect);
- continue;
}
/* regular window moving */
RECTL_vOffsetRect(&newRect, dx, dy);
}
- if (ON_LEFT_BORDER(hittest)) newRect.left += dx;
- else if (ON_RIGHT_BORDER(hittest)) newRect.right += dx;
- if (ON_TOP_BORDER(hittest)) newRect.top += dy;
- else if (ON_BOTTOM_BORDER(hittest)) newRect.bottom += dy;
- capturePoint = pt;
+ if (ON_LEFT_BORDER(hittest)) newRect.left += dx;
+ else if (ON_RIGHT_BORDER(hittest)) newRect.right += dx;
+ if (ON_TOP_BORDER(hittest)) newRect.top += dy;
+ else if (ON_BOTTOM_BORDER(hittest)) newRect.bottom += dy;
+
+ capturePoint = pt;
//
// Save the new position to the unmodified rectangle. This allows explorer
task bar
@@ -549,7 +565,7 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
//
unmodRect = newRect;
- /* determine the hit location */
+ /* Determine the hit location */
if (syscommand == SC_SIZE)
{
WPARAM wpSizingHit = 0;
@@ -561,7 +577,7 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
else
co_IntSendMessage( UserHMGetHandle(pwnd), WM_MOVING, 0,
(LPARAM)&newRect );
- if (!iconic)
+ if (!iconic)
{
if (!DragFullWindows)
UserDrawMovingFrame( hdc, &newRect, thickframe );
@@ -609,7 +625,7 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
}
}
sizingRect = newRect;
- }
+ }
}
}
@@ -631,8 +647,12 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
*/
if (OldCursor) UserDereferenceObject(OldCursor);
}
- else if ( moved && !DragFullWindows )
- UserDrawMovingFrame( hdc, &sizingRect, thickframe );
+ else
+ {
+ UINT eraseFinalFrame = moved && !DragFullWindows;
+ if (eraseFinalFrame)
+ UserDrawMovingFrame(hdc, pFrameRect, thickframe); // Undo the XOR drawing
+ }
UserReleaseDC(NULL, hdc, FALSE);
@@ -656,49 +676,57 @@ DefWndDoSizeMove(PWND pwnd, WORD wParam)
/* window moved or resized */
if (moved)
{
+ BOOL forceSizing = !iconic && hittest == HTCAPTION && (!!orgSnap !=
!!snap);
+ UINT swp = (!forceSizing && hittest == HTCAPTION) ? SWP_NOSIZE : 0;
+
/* if the moving/resizing isn't canceled call SetWindowPos
* with the new position or the new size of the window
*/
if (!((msg.message == WM_KEYDOWN) && (msg.wParam == VK_ESCAPE)) )
{
- /* NOTE: SWP_NOACTIVATE prevents document window activation in Word 6 */
- if (!DragFullWindows || iconic )
- {
- co_WinPosSetWindowPos( pwnd,
- 0,
- sizingRect.left,
- sizingRect.top,
- sizingRect.right - sizingRect.left,
- sizingRect.bottom - sizingRect.top,
- ( hittest == HTCAPTION ) ? SWP_NOSIZE : 0 );
- }
+ /* NOTE: SWP_NOACTIVATE prevents document window activation in Word 6 */
+ if (!DragFullWindows || iconic)
+ {
+ if (snap)
+ {
+ co_IntSnapWindow(pwnd, snap);
+ }
+ else
+ {
+ if (orgSnap && !snap)
+ {
+ IntSetStyle(pwnd, 0, WS_MAXIMIZE);
+ IntSetSnapInfo(pwnd, HTNOWHERE, NULL);
+ }
+ co_WinPosSetWindowPos(pwnd, HWND_TOP, sizingRect.left, sizingRect.top,
+ sizingRect.right - sizingRect.left,
+ sizingRect.bottom - sizingRect.top, swp);
+ }
+ }
}
else
- { /* restore previous size/position */
- if ( DragFullWindows )
- {
- co_WinPosSetWindowPos( pwnd,
- 0,
- origRect.left,
- origRect.top,
- origRect.right - origRect.left,
- origRect.bottom - origRect.top,
- ( hittest == HTCAPTION ) ? SWP_NOSIZE : 0 );
- }
+ {
+ /* restore previous size/position */
+ if (orgSnap)
+ {
+ co_IntSnapWindow(pwnd, orgSnap);
+ }
+ else if (DragFullWindows)
+ {
+ co_WinPosSetWindowPos(pwnd, HWND_TOP, origRect.left, origRect.top,
+ origRect.right - origRect.left,
+ origRect.bottom - origRect.top, swp);
+ }
}
}
- if ( IntIsWindow(UserHMGetHandle(pwnd)) )
+ if (IntIsWindow(UserHMGetHandle(pwnd)))
{
- if ( iconic )
- {
- /* Single click brings up the system menu when iconized */
- if ( !moved )
- {
- if( Style & WS_SYSMENU )
- co_IntSendMessage( UserHMGetHandle(pwnd), WM_SYSCOMMAND, SC_MOUSEMENU + HTSYSMENU,
MAKELONG(pt.x,pt.y));
- }
- }
+ /* Single click brings up the system menu when iconized */
+ if (iconic && !moved && (Style & WS_SYSMENU))
+ {
+ co_IntSendMessage(UserHMGetHandle(pwnd), WM_SYSCOMMAND, SC_MOUSEMENU +
HTSYSMENU, MAKELONG(pt.x, pt.y));
+ }
}
}
diff --git a/win32ss/user/ntuser/winpos.c b/win32ss/user/ntuser/winpos.c
index 227b03e8a5c..9c55ef7c0d9 100644
--- a/win32ss/user/ntuser/winpos.c
+++ b/win32ss/user/ntuser/winpos.c
@@ -549,7 +549,13 @@ WinPosInitInternalPos(PWND Wnd, RECTL *RestoreRect)
}
else
{
- Wnd->InternalPos.NormalRect = Rect;
+ /* Lie about the snap; Windows does this so applications don't save their
+ * position as a snap but rather the unsnapped "real" position. */
+ if (!IntIsWindowSnapped(Wnd) ||
+ RECTL_bIsEmptyRect(&Wnd->InternalPos.NormalRect))
+ {
+ Wnd->InternalPos.NormalRect = Rect;
+ }
}
}
@@ -2487,6 +2493,7 @@ co_WinPosMinMaximize(PWND Wnd, UINT ShowFlag, RECT* NewPos)
case SW_MAXIMIZE:
{
//ERR("MinMaximize Maximize\n");
+ IntSetSnapEdge(Wnd, HTNOWHERE); /* Mark as not snapped (for
Win+Left,Up,Down) */
if ((Wnd->style & WS_MAXIMIZE) && (Wnd->style &
WS_VISIBLE))
{
SwpFlags = SWP_NOSIZE | SWP_NOMOVE;
@@ -2535,6 +2542,12 @@ co_WinPosMinMaximize(PWND Wnd, UINT ShowFlag, RECT* NewPos)
else
{
*NewPos = wpl.rcNormalPosition;
+ if (ShowFlag != SW_SHOWNORMAL && ShowFlag !=
SW_SHOWDEFAULT)
+ {
+ UINT edge = IntGetWindowSnapEdge(Wnd);
+ if (edge)
+ co_IntCalculateSnapPosition(Wnd, edge, NewPos);
+ }
NewPos->right -= NewPos->left;
NewPos->bottom -= NewPos->top;
break;
@@ -3863,4 +3876,139 @@ NtUserWindowFromPoint(LONG X, LONG Y)
return Ret;
}
+/* Windows 10 (1903?)
+BOOL APIENTRY
+NtUserIsWindowArranged(HWND hWnd)
+{
+ PWND pwnd = UserGetWindowObject(hWnd);
+ return pwnd && IntIsWindowSnapped(pwnd);
+}
+*/
+
+UINT FASTCALL
+IntGetWindowSnapEdge(PWND Wnd)
+{
+ if (Wnd->ExStyle2 & WS_EX2_VERTICALLYMAXIMIZEDLEFT) return HTLEFT;
+ if (Wnd->ExStyle2 & WS_EX2_VERTICALLYMAXIMIZEDRIGHT) return HTRIGHT;
+ return HTNOWHERE;
+}
+
+VOID FASTCALL
+co_IntCalculateSnapPosition(PWND Wnd, UINT Edge, OUT RECT *Pos)
+{
+ POINT maxs, mint, maxt;
+ UINT width, height;
+ UserSystemParametersInfo(SPI_GETWORKAREA, 0, Pos, 0); /* FIXME: MultiMon of PWND */
+
+ co_WinPosGetMinMaxInfo(Wnd, &maxs, NULL, &mint, &maxt);
+ width = Pos->right - Pos->left;
+ width = min(min(max(width / 2, mint.x), maxt.x), width);
+ height = Pos->bottom - Pos->top;
+ height = min(max(height, mint.y), maxt.y);
+
+ switch (Edge)
+ {
+ case HTTOP: /* Maximized (Calculate RECT snap preview for SC_MOVE) */
+ height = min(Pos->bottom - Pos->top, maxs.y);
+ break;
+ case HTLEFT:
+ Pos->right = width;
+ break;
+ case HTRIGHT:
+ Pos->left = Pos->right - width;
+ break;
+ default:
+ ERR("Unexpected snap edge %#x\n", Edge);
+ }
+ Pos->bottom = Pos->top + height;
+}
+
+VOID FASTCALL
+co_IntSnapWindow(PWND Wnd, UINT Edge)
+{
+ RECT newPos;
+ BOOLEAN wasSnapped = IntIsWindowSnapped(Wnd);
+ UINT normal = !(Wnd->style & (WS_MAXIMIZE | WS_MINIMIZE));
+ USER_REFERENCE_ENTRY ref;
+ BOOLEAN hasRef = FALSE;
+
+ if (Edge == HTTOP)
+ {
+ co_IntSendMessage(UserHMGetHandle(Wnd), WM_SYSCOMMAND, SC_MAXIMIZE, 0);
+ return;
+ }
+ else if (Edge)
+ {
+ UserRefObjectCo(Wnd, &ref);
+ hasRef = TRUE;
+ co_IntCalculateSnapPosition(Wnd, Edge, &newPos);
+ IntSetSnapInfo(Wnd, Edge, (wasSnapped || !normal) ? NULL :
&Wnd->rcWindow);
+ }
+ else if (wasSnapped)
+ {
+ if (!normal)
+ {
+ IntSetSnapEdge(Wnd, HTNOWHERE);
+ return;
+ }
+ newPos = Wnd->InternalPos.NormalRect;
+ IntSetSnapInfo(Wnd, HTNOWHERE, NULL);
+ }
+ else
+ {
+ return; /* Already unsnapped, do nothing */
+ }
+
+ TRACE("WindowSnap: %d->%d\n", IntGetWindowSnapEdge(Wnd), Edge);
+ co_WinPosSetWindowPos(Wnd, HWND_TOP,
+ newPos.left,
+ newPos.top,
+ newPos.right - newPos.left,
+ newPos.bottom - newPos.top,
+ 0);
+ if (hasRef)
+ UserDerefObjectCo(Wnd);
+}
+
+VOID FASTCALL
+IntSetSnapEdge(PWND Wnd, UINT Edge)
+{
+ UINT styleMask = WS_EX2_VERTICALLYMAXIMIZEDLEFT | WS_EX2_VERTICALLYMAXIMIZEDRIGHT;
+ UINT style = 0;
+ switch (Edge)
+ {
+ case HTNOWHERE:
+ style = 0;
+ break;
+ case HTTOP: /* Maximize throws away the snap */
+ style = 0;
+ break;
+ case HTLEFT:
+ style = WS_EX2_VERTICALLYMAXIMIZEDLEFT;
+ break;
+ case HTRIGHT:
+ style = WS_EX2_VERTICALLYMAXIMIZEDRIGHT;
+ break;
+ default:
+ ERR("Unexpected snap edge %#x\n", Edge);
+ }
+ Wnd->ExStyle2 = (Wnd->ExStyle2 & ~styleMask) | style;
+}
+
+VOID FASTCALL
+IntSetSnapInfo(PWND Wnd, UINT Edge, IN const RECT *Pos OPTIONAL)
+{
+ RECT r;
+ IntSetSnapEdge(Wnd, Edge);
+ if (Edge != HTNOWHERE)
+ {
+ RECTL_vSetEmptyRect(&r);
+ Pos = (Wnd->style & WS_MINIMIZE) ? NULL : &r;
+ }
+ if (Pos)
+ {
+ Wnd->InternalPos.NormalRect = *Pos;
+ }
+}
+
/* EOF */
diff --git a/win32ss/user/ntuser/winpos.h b/win32ss/user/ntuser/winpos.h
index 03e1cd8923b..17d1c49cf33 100644
--- a/win32ss/user/ntuser/winpos.h
+++ b/win32ss/user/ntuser/winpos.h
@@ -71,3 +71,34 @@ BOOL FASTCALL IntClientToScreen(PWND,LPPOINT);
BOOL FASTCALL IntGetWindowRect(PWND,RECTL*);
BOOL UserHasWindowEdge(DWORD,DWORD);
VOID UserGetWindowBorders(DWORD,DWORD,SIZE*,BOOL);
+
+UINT FASTCALL IntGetWindowSnapEdge(PWND Wnd);
+VOID FASTCALL co_IntCalculateSnapPosition(PWND Wnd, UINT Edge, OUT RECT *Pos);
+VOID FASTCALL co_IntSnapWindow(PWND Wnd, UINT Edge);
+VOID FASTCALL IntSetSnapEdge(PWND Wnd, UINT Edge);
+VOID FASTCALL IntSetSnapInfo(PWND Wnd, UINT Edge, IN const RECT *Pos OPTIONAL);
+
+FORCEINLINE VOID
+co_IntUnsnapWindow(PWND Wnd)
+{
+ co_IntSnapWindow(Wnd, 0);
+}
+
+FORCEINLINE BOOLEAN
+IntIsWindowSnapped(PWND Wnd)
+{
+ return (Wnd->ExStyle2 & (WS_EX2_VERTICALLYMAXIMIZEDLEFT |
WS_EX2_VERTICALLYMAXIMIZEDRIGHT)) != 0;
+}
+
+FORCEINLINE BOOLEAN
+IntIsSnapAllowedForWindow(PWND Wnd)
+{
+ /* We want to forbid snapping operations on the TaskBar and on child windows.
+ * We use a heuristic for detecting the TaskBar by its typical Style & ExStyle.
*/
+ const UINT style = Wnd->style;
+ const UINT tbws = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ const UINT tbes = WS_EX_TOOLWINDOW;
+ BOOLEAN istb = (style & tbws) == tbws && (Wnd->ExStyle & (tbes |
WS_EX_APPWINDOW)) == tbes;
+ BOOLEAN thickframe = (style & WS_THICKFRAME) && (style & (WS_DLGFRAME
| WS_BORDER)) != WS_DLGFRAME;
+ return thickframe && !(style & WS_CHILD) && !istb;
+}