1010
1111#include " resource.h"
1212
13+ #include < dwmapi.h>
14+
1315TRACELOGGING_DEFINE_PROVIDER (
1416 g_hProvider,
1517 " Microsoft.PowerToys" ,
@@ -21,6 +23,7 @@ TRACELOGGING_DEFINE_PROVIDER(
2123// Globals
2224// ---------------------------------------------------------------------------
2325static HINSTANCE g_hInstance = nullptr ;
26+ static ULONG_PTR g_gdiplusToken = 0 ; // GDI+ token for overlay border rendering
2427static HHOOK g_hhkKeyboard = nullptr ;
2528static HHOOK g_hhkMouse = nullptr ;
2629static HWND g_hMsgWnd = nullptr ;
@@ -47,6 +50,29 @@ static HWND g_hOverlay = nullptr; // semi-transparent overlay during drag
4750static int g_overlayInfoX = 0 , g_overlayInfoY = 0 ;
4851static int g_overlayInfoW = 0 , g_overlayInfoH = 0 ;
4952
53+ // Visible-frame overlay metrics. Computed once per drag/resize (cold path) and
54+ // reused while rendering - never recomputed in the mouse-move hot path.
55+ // Margins are the difference between GetWindowRect and the DWM extended frame
56+ // bounds (the invisible resize border), so the fill and border hug the visible
57+ // window. The border is drawn just inside the visible edge; Always On Top draws
58+ // its own border just outside that edge, so the two stack into a clean double
59+ // layer without Grab and Move having to widen its stroke.
60+ static int g_overlayMarginL = 0 , g_overlayMarginT = 0 , g_overlayMarginR = 0 , g_overlayMarginB = 0 ;
61+ static int g_overlayCornerRadius = 0 ; // physical px; 0 = square corners
62+ static int g_overlayBorderThickness = 4 ; // physical px
63+
64+ // Fluent "warning" gold - the literal equivalent of WinUI SystemFillColorCaution
65+ // (used as a ThemeResource for warnings across the Settings UI). A Win32 layered
66+ // window can't resolve a ThemeResource, so the literal is required here.
67+ static constexpr COLORREF OVERLAY_BORDER_COLOR = RGB (255 , 185 , 0 ); // #FFB900
68+
69+ // Border thickness in DIPs (scaled by the target window DPI).
70+ static constexpr int OVERLAY_BORDER_DIP = 4 ;
71+
72+ // Translucent white wash painted over the visible window during a drag/resize,
73+ // matching the prior overlay. ~40% opacity (premultiplied white = 0x66666666).
74+ static constexpr BYTE OVERLAY_FILL_ALPHA = 0x66 ;
75+
5076static bool g_shouldAbsorbAlt =
5177 true ; // true if we want to absorb Alt on the next keydown (set when Alt is pressed without dragging, cleared on next non-Alt key or Alt keyup)
5278static bool g_altAbsorbed = false ; // true if we absorbed an Alt keydown
@@ -343,9 +369,122 @@ static void SettingsWatcherThread(DWORD mainThreadId)
343369static int g_overlayRenderedW = 0 ;
344370static int g_overlayRenderedH = 0 ;
345371
372+ // Maps the DWM window corner preference to a base radius in DIPs, matching
373+ // Always On Top (WindowCornerUtils::CornersRadius).
374+ static int CornerRadiusForWindow (HWND hwnd)
375+ {
376+ int pref = 0 ; // DWMWCP_DEFAULT
377+ if (DwmGetWindowAttribute (hwnd, DWMWA_WINDOW_CORNER_PREFERENCE , &pref, sizeof (pref)) != S_OK )
378+ {
379+ return 0 ; // pre-Win11 / unsupported -> square corners
380+ }
381+
382+ switch (pref)
383+ {
384+ case DWMWCP_ROUND :
385+ return 8 ;
386+ case DWMWCP_ROUNDSMALL :
387+ return 4 ;
388+ case DWMWCP_DEFAULT :
389+ return 8 ;
390+ default :
391+ return 0 ; // DWMWCP_DONOTROUND
392+ }
393+ }
394+
395+ // Computes the overlay metrics (margins to the visible frame, corner radius, border
396+ // thickness) for the target window. Cold path only: called at the start of a
397+ // drag/resize and after un-maximize, never from the mouse-move hot path.
398+ static void PrepareOverlayMetrics (HWND target)
399+ {
400+ g_overlayMarginL = g_overlayMarginT = g_overlayMarginR = g_overlayMarginB = 0 ;
401+ g_overlayCornerRadius = 0 ;
402+ g_overlayBorderThickness = OVERLAY_BORDER_DIP ;
403+
404+ if (!target)
405+ {
406+ return ;
407+ }
408+
409+ const UINT dpi = GetDpiForWindow (target);
410+ const float scale = (dpi != 0 ) ? dpi / 96 .0f : 1 .0f ;
411+
412+ RECT windowRect{};
413+ RECT frameRect{};
414+ if (GetWindowRect (target, &windowRect) &&
415+ SUCCEEDED (DwmGetWindowAttribute (target, DWMWA_EXTENDED_FRAME_BOUNDS , &frameRect, sizeof (frameRect))))
416+ {
417+ g_overlayMarginL = max (0 , static_cast <int >(frameRect.left - windowRect.left ));
418+ g_overlayMarginT = max (0 , static_cast <int >(frameRect.top - windowRect.top ));
419+ g_overlayMarginR = max (0 , static_cast <int >(windowRect.right - frameRect.right ));
420+ g_overlayMarginB = max (0 , static_cast <int >(windowRect.bottom - frameRect.bottom ));
421+ }
422+
423+ g_overlayCornerRadius = static_cast <int >(CornerRadiusForWindow (target) * scale);
424+ g_overlayBorderThickness = static_cast <int >(OVERLAY_BORDER_DIP * scale);
425+ }
426+
427+ // Draws an antialiased (optionally rounded) border stroke fully inside `rect` using
428+ // GDI+. The stroke hugs the inner edge of `rect` (the visible window frame).
429+ static void DrawOverlayBorder (Gdiplus::Graphics& graphics, const RECT & rect, int thickness, int radius)
430+ {
431+ const int w = rect.right - rect.left ;
432+ const int h = rect.bottom - rect.top ;
433+ if (w <= 0 || h <= 0 || thickness <= 0 )
434+ {
435+ return ;
436+ }
437+
438+ // Keep the whole stroke inside the visible frame on every side.
439+ thickness = min (thickness, min (w, h) / 2 );
440+ if (thickness <= 0 )
441+ {
442+ return ;
443+ }
444+
445+ const float half = thickness / 2 .0f ;
446+ const Gdiplus::RectF path (
447+ rect.left + half,
448+ rect.top + half,
449+ static_cast <Gdiplus::REAL >(w) - thickness,
450+ static_cast <Gdiplus::REAL >(h) - thickness);
451+
452+ graphics.SetSmoothingMode (Gdiplus::SmoothingModeAntiAlias);
453+ Gdiplus::Pen pen (
454+ Gdiplus::Color (255 , GetRValue (OVERLAY_BORDER_COLOR ), GetGValue (OVERLAY_BORDER_COLOR ), GetBValue (OVERLAY_BORDER_COLOR )),
455+ static_cast <Gdiplus::REAL >(thickness));
456+
457+ if (radius <= 0 )
458+ {
459+ graphics.DrawRectangle (&pen, path);
460+ return ;
461+ }
462+
463+ // The stroke is centred, so the path corner radius is the window radius minus
464+ // half the thickness; that keeps the outer edge aligned with the window corner.
465+ const float pathRadius = max (0 .0f , radius - half);
466+ const float diameter = min (pathRadius * 2 .0f , min (path.Width , path.Height ));
467+ if (diameter <= 0 .0f )
468+ {
469+ graphics.DrawRectangle (&pen, path);
470+ return ;
471+ }
472+
473+ Gdiplus::GraphicsPath border;
474+ border.AddArc (path.X , path.Y , diameter, diameter, 180 .0f , 90 .0f );
475+ border.AddArc (path.GetRight () - diameter, path.Y , diameter, diameter, 270 .0f , 90 .0f );
476+ border.AddArc (path.GetRight () - diameter, path.GetBottom () - diameter, diameter, diameter, 0 .0f , 90 .0f );
477+ border.AddArc (path.X , path.GetBottom () - diameter, diameter, diameter, 90 .0f , 90 .0f );
478+ border.CloseFigure ();
479+ graphics.DrawPath (&pen, &border);
480+ }
481+
482+ // Renders the overlay surface using per-pixel alpha via UpdateLayeredWindow.
346483// Renders the overlay surface using per-pixel alpha via UpdateLayeredWindow.
347- // The white background is painted at ~40% opacity; the geometry label box is
348- // painted fully opaque so it remains legible regardless of what is beneath.
484+ // A translucent white wash covers the visible window (matching the prior overlay)
485+ // with a tight warning-gold border on top, both hugging the visible window frame;
486+ // the optional geometry label box is painted fully opaque so it remains legible
487+ // regardless of what is beneath.
349488static void RenderOverlayContent (HWND hwnd, int cw, int ch)
350489{
351490 if (!hwnd || cw <= 0 || ch <= 0 )
@@ -372,8 +511,52 @@ static void RenderOverlayContent(HWND hwnd, int cw, int ch)
372511 HDC memDC = CreateCompatibleDC (screenDC);
373512 HBITMAP hOldBmp = static_cast <HBITMAP >(SelectObject (memDC, hDib));
374513
375- // Premultiplied white at ~40% opacity: A=0x66, R=G=B=0x66 → 0x66666666
376- memset (pBits, 0x66 , static_cast <size_t >(cw) * ch * sizeof (DWORD ));
514+ // Start fully transparent.
515+ memset (pBits, 0 , static_cast <size_t >(cw) * ch * sizeof (DWORD ));
516+
517+ // Translucent white wash over the visible window (prior-system fill) with a tight
518+ // warning-gold border on top. The overlay window spans GetWindowRect, so inset by
519+ // the invisible-border margins so both hug the visible edge; Always On Top draws
520+ // its own border just outside that edge, giving a clean double layer.
521+ {
522+ const RECT visible = {
523+ g_overlayMarginL,
524+ g_overlayMarginT,
525+ cw - g_overlayMarginR,
526+ ch - g_overlayMarginB
527+ };
528+ const int vw = visible.right - visible.left ;
529+ const int vh = visible.bottom - visible.top ;
530+
531+ Gdiplus::Bitmap bitmap (cw, ch, cw * 4 , PixelFormat32bppPARGB, reinterpret_cast <BYTE *>(pBits));
532+ Gdiplus::Graphics graphics (&bitmap);
533+ graphics.SetSmoothingMode (Gdiplus::SmoothingModeAntiAlias);
534+
535+ if (vw > 0 && vh > 0 )
536+ {
537+ Gdiplus::SolidBrush fillBrush (Gdiplus::Color (OVERLAY_FILL_ALPHA , 255 , 255 , 255 ));
538+ if (g_overlayCornerRadius > 0 )
539+ {
540+ // Round the wash to match the window corners (and the border).
541+ const float d = min (static_cast <float >(g_overlayCornerRadius) * 2 .0f ,
542+ static_cast <float >(min (vw, vh)));
543+ Gdiplus::GraphicsPath fillPath;
544+ fillPath.AddArc (static_cast <float >(visible.left ), static_cast <float >(visible.top ), d, d, 180 .0f , 90 .0f );
545+ fillPath.AddArc (static_cast <float >(visible.right ) - d, static_cast <float >(visible.top ), d, d, 270 .0f , 90 .0f );
546+ fillPath.AddArc (static_cast <float >(visible.right ) - d, static_cast <float >(visible.bottom ) - d, d, d, 0 .0f , 90 .0f );
547+ fillPath.AddArc (static_cast <float >(visible.left ), static_cast <float >(visible.bottom ) - d, d, d, 90 .0f , 90 .0f );
548+ fillPath.CloseFigure ();
549+ graphics.FillPath (&fillBrush, &fillPath);
550+ }
551+ else
552+ {
553+ graphics.FillRectangle (&fillBrush, visible.left , visible.top , vw, vh);
554+ }
555+ }
556+
557+ DrawOverlayBorder (graphics, visible, g_overlayBorderThickness, g_overlayCornerRadius);
558+ graphics.Flush ();
559+ }
377560
378561 if (g_showGeometry)
379562 {
@@ -1045,6 +1228,7 @@ static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
10451228 g_dragTarget = hwnd;
10461229 g_dragStart = pt;
10471230 GetWindowRect (hwnd, &g_dragWndRect);
1231+ PrepareOverlayMetrics (hwnd);
10481232
10491233 // Show the semi-transparent overlay on top of the target (persistent window – fix #9)
10501234 ShowOverlay (g_dragWndRect, g_curSizeAll);
@@ -1112,6 +1296,7 @@ static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
11121296 g_resizeTarget = hwnd;
11131297 g_resizeLast = pt;
11141298 GetWindowRect (hwnd, &g_resizeWndRect);
1299+ PrepareOverlayMetrics (hwnd);
11151300
11161301 g_currentHandle = GetClosestHandle (pt, g_resizeWndRect);
11171302 ShowOverlay (g_resizeWndRect, CursorForHandle (g_currentHandle));
@@ -1183,6 +1368,9 @@ static void HandleDragMove(POINT pt)
11831368
11841369 g_dragStart = pt;
11851370 g_dragWndRect = {newX, newY, newX + restoredW, newY + restoredH};
1371+
1372+ // Corner radius / invisible-border margins differ once restored.
1373+ PrepareOverlayMetrics (g_dragTarget);
11861374 }
11871375 }
11881376
@@ -1230,6 +1418,9 @@ static void HandleDragResize(POINT pt)
12301418 SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS );
12311419 g_resizeWndRect = {newLeft, newTop, newLeft + newW, newTop + newH};
12321420
1421+ // Corner radius / invisible-border margins differ once restored.
1422+ PrepareOverlayMetrics (g_resizeTarget);
1423+
12331424 g_resizeLast = pt;
12341425 g_currentHandle = GetClosestHandle (pt, g_resizeWndRect);
12351426 }
@@ -1375,6 +1566,10 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
13751566 INITCOMMONCONTROLSEX commonControls = { sizeof (commonControls), ICC_STANDARD_CLASSES };
13761567 InitCommonControlsEx (&commonControls);
13771568
1569+ // Initialise GDI+ for antialiased overlay border rendering
1570+ Gdiplus::GdiplusStartupInput gdiplusStartupInput;
1571+ Gdiplus::GdiplusStartup (&g_gdiplusToken, &gdiplusStartupInput, nullptr );
1572+
13781573 // Register a message-only window class
13791574 WNDCLASSEXW wc = {};
13801575 wc.cbSize = sizeof (wc);
@@ -1387,13 +1582,13 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
13871582 return 1 ;
13881583 }
13891584
1390- // Register the overlay window class (white background , ARROW cursor)
1585+ // Register the overlay window class (layered per-pixel-alpha surface , ARROW cursor)
13911586 WNDCLASSEXW overlayWindowClass = {};
13921587 overlayWindowClass.cbSize = sizeof (overlayWindowClass);
13931588 overlayWindowClass.lpfnWndProc = DefWindowProcW;
13941589 overlayWindowClass.hInstance = hInstance;
13951590 overlayWindowClass.hCursor = LoadCursorW (nullptr , IDC_ARROW );
1396- overlayWindowClass.hbrBackground = static_cast < HBRUSH >( GetStockObject ( WHITE_BRUSH ));
1591+ overlayWindowClass.hbrBackground = nullptr ; // per-pixel alpha via UpdateLayeredWindow
13971592 overlayWindowClass.lpszClassName = OVERLAY_CLASS_NAME ;
13981593 if (!RegisterClassExW (&overlayWindowClass))
13991594 {
@@ -1482,6 +1677,11 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
14821677 g_hOverlay = nullptr ;
14831678 }
14841679 RemoveTrayIcon ();
1680+ if (g_gdiplusToken)
1681+ {
1682+ Gdiplus::GdiplusShutdown (g_gdiplusToken);
1683+ g_gdiplusToken = 0 ;
1684+ }
14851685 TraceLoggingUnregister (g_hProvider);
14861686
14871687 return static_cast <int >(msg.wParam );
0 commit comments