[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
This commit is contained in:
Whindmar Saksit 2024-08-11 20:21:58 +02:00 committed by GitHub
parent 6fb67ddc0e
commit f3d03760e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 371 additions and 210 deletions

View File

@ -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;
/* 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 */
}
windowRect = topWnd->rcWindow;
#if 0 /* Windows 8 does this but is it a good feature? */
else if (snapped == edge)
{
/* Already snapped to this edge, snap to the opposite side */
edge = otherEdge;
}
#endif
UserSystemParametersInfo(SPI_GETWORKAREA, 0, &snapRect, 0);
if (wParam == VK_LEFT)
{
snapRect.right = (snapRect.left + snapRect.right) / 2;
}
else // VK_RIGHT
{
snapRect.left = (snapRect.left + snapRect.right) / 2;
}
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);
}
}
}

View File

@ -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)
BOOLEAN wasSnap = IntIsWindowSnapped(pwnd); /* Need the live snap state, not orgSnap nor maximized state */
UINT snapTo = iconic ? HTNOWHERE : GetSnapActivationPoint(pwnd, pt);
if (snapTo)
{
snapRect.right = (snapRect.right - snapRect.left) / 2 + snapRect.left;
doSideSnap = TRUE;
}
// snap to right
if (pt.x >= snapRect.right-1)
{
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);
*pr = pwnd->InternalPos.NormalRect;
origRect = *pr; /* Save normal size - is required when window unsnapped from one side and snapped to another holding mouse down */
/* Save normal size - it required when window unsnapped from one side and snapped to another holding mouse down */
origRect = pwnd->InternalPos.NormalRect;
/* 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)
{
pr->left = mouseRect.left;
pr->right = pr->left + width;
}
if ((pwnd->ExStyle & WS_EX_LAYOUTRTL) && pr->right > mouseRect.right)
{
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);
/* Restore from maximized state */
if (Style & WS_MAXIMIZE)
{
co_IntSendMessage(UserHMGetHandle(pwnd), WM_SYSCOMMAND, SC_RESTORE, 0);
/* 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);
}
/* Restore snapped to left/right place */
else
}
else if (!snap && syscommand == SC_MOVE && !iconic)
{
if ((snapTo = GetSnapActivationPoint(pwnd, pt)) != 0)
{
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);
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));
}
}
}

View File

@ -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 */

View File

@ -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;
}