55#include " MouseHighlighter.h"
66#include " trace.h"
77#include < cmath>
8+ #include < algorithm>
89
910#ifdef COMPOSITION
1011namespace winrt
@@ -49,6 +50,9 @@ struct Highlighter
4950 void BringToFront ();
5051 HHOOK m_mouseHook = NULL ;
5152 static LRESULT CALLBACK MouseHookProc (int nCode, WPARAM wParam, LPARAM lParam) noexcept ;
53+ // Helpers for spotlight overlay
54+ float GetDpiScale () const ;
55+ void UpdateSpotlightMask (float cx, float cy, float radius, bool show);
5256
5357 static constexpr auto m_className = L" MouseHighlighter" ;
5458 static constexpr auto m_windowTitle = L" PowerToys Mouse Highlighter" ;
@@ -67,7 +71,14 @@ struct Highlighter
6771 winrt::CompositionSpriteShape m_leftPointer{ nullptr };
6872 winrt::CompositionSpriteShape m_rightPointer{ nullptr };
6973 winrt::CompositionSpriteShape m_alwaysPointer{ nullptr };
70- winrt::CompositionSpriteShape m_spotlightPointer{ nullptr };
74+ // Spotlight overlay (mask with soft feathered edge)
75+ winrt::SpriteVisual m_overlay{ nullptr };
76+ winrt::CompositionMaskBrush m_spotlightMask{ nullptr };
77+ winrt::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
78+ winrt::CompositionColorBrush m_spotlightSource{ nullptr };
79+ winrt::CompositionColorGradientStop m_maskStopCenter{ nullptr };
80+ winrt::CompositionColorGradientStop m_maskStopInner{ nullptr };
81+ winrt::CompositionColorGradientStop m_maskStopOuter{ nullptr };
7182
7283 bool m_leftPointerEnabled = true ;
7384 bool m_rightPointerEnabled = true ;
@@ -123,6 +134,35 @@ bool Highlighter::CreateHighlighter()
123134 m_shape.RelativeSizeAdjustment ({ 1 .0f , 1 .0f });
124135 m_root.Children ().InsertAtTop (m_shape);
125136
137+ // Create spotlight overlay (soft feather, DPI-aware)
138+ m_overlay = m_compositor.CreateSpriteVisual ();
139+ m_overlay.RelativeSizeAdjustment ({ 1 .0f , 1 .0f });
140+ m_spotlightSource = m_compositor.CreateColorBrush (m_alwaysColor);
141+ m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush ();
142+ m_spotlightMaskGradient.MappingMode (winrt::CompositionMappingMode::Absolute);
143+ // Center region fully transparent
144+ m_maskStopCenter = m_compositor.CreateColorGradientStop ();
145+ m_maskStopCenter.Offset (0 .0f );
146+ m_maskStopCenter.Color (winrt::Windows::UI::ColorHelper::FromArgb (0 , 0 , 0 , 0 ));
147+ // Inner edge of feather (still transparent)
148+ m_maskStopInner = m_compositor.CreateColorGradientStop ();
149+ m_maskStopInner.Offset (0 .995f ); // will be updated per-radius
150+ m_maskStopInner.Color (winrt::Windows::UI::ColorHelper::FromArgb (0 , 0 , 0 , 0 ));
151+ // Outer edge (opaque mask -> overlay visible)
152+ m_maskStopOuter = m_compositor.CreateColorGradientStop ();
153+ m_maskStopOuter.Offset (1 .0f );
154+ m_maskStopOuter.Color (winrt::Windows::UI::ColorHelper::FromArgb (255 , 255 , 255 , 255 ));
155+ m_spotlightMaskGradient.ColorStops ().Append (m_maskStopCenter);
156+ m_spotlightMaskGradient.ColorStops ().Append (m_maskStopInner);
157+ m_spotlightMaskGradient.ColorStops ().Append (m_maskStopOuter);
158+
159+ m_spotlightMask = m_compositor.CreateMaskBrush ();
160+ m_spotlightMask.Source (m_spotlightSource);
161+ m_spotlightMask.Mask (m_spotlightMaskGradient);
162+ m_overlay.Brush (m_spotlightMask);
163+ m_overlay.IsVisible (false );
164+ m_root.Children ().InsertAtTop (m_overlay);
165+
126166 return true ;
127167 }
128168 catch (...)
@@ -165,12 +205,8 @@ void Highlighter::AddDrawingPoint(MouseButton button)
165205 // always
166206 if (m_spotlightMode)
167207 {
168- float borderThickness = static_cast <float >(std::hypot (GetSystemMetrics (SM_CXVIRTUALSCREEN), GetSystemMetrics (SM_CYVIRTUALSCREEN)));
169- circleGeometry.Radius ({ static_cast <float >(borderThickness / 2.0 + m_radius), static_cast <float >(borderThickness / 2.0 + m_radius) });
170- circleShape.FillBrush (nullptr );
171- circleShape.StrokeBrush (m_compositor.CreateColorBrush (m_alwaysColor));
172- circleShape.StrokeThickness (borderThickness);
173- m_spotlightPointer = circleShape;
208+ UpdateSpotlightMask (static_cast <float >(pt.x ), static_cast <float >(pt.y ), m_radius, true );
209+ return ;
174210 }
175211 else
176212 {
@@ -209,20 +245,14 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
209245 }
210246 else
211247 {
212- // always
248+ // always / spotlight idle
213249 if (m_spotlightMode)
214250 {
215- if (m_spotlightPointer)
216- {
217- m_spotlightPointer.Offset ({ static_cast <float >(pt.x ), static_cast <float >(pt.y ) });
218- }
251+ UpdateSpotlightMask (static_cast <float >(pt.x ), static_cast <float >(pt.y ), m_radius, true );
219252 }
220- else
253+ else if (m_alwaysPointer)
221254 {
222- if (m_alwaysPointer)
223- {
224- m_alwaysPointer.Offset ({ static_cast <float >(pt.x ), static_cast <float >(pt.y ) });
225- }
255+ m_alwaysPointer.Offset ({ static_cast <float >(pt.x ), static_cast <float >(pt.y ) });
226256 }
227257 }
228258}
@@ -266,9 +296,9 @@ void Highlighter::ClearDrawingPoint()
266296{
267297 if (m_spotlightMode)
268298 {
269- if (m_spotlightPointer )
299+ if (m_overlay )
270300 {
271- m_spotlightPointer. StrokeBrush (). as <winrt::Windows::UI::Composition::CompositionColorBrush>(). Color ( winrt::Windows::UI::ColorHelper::FromArgb ( 0 , 0 , 0 , 0 ) );
301+ m_overlay. IsVisible ( false );
272302 }
273303 }
274304 else
@@ -421,7 +451,10 @@ void Highlighter::StopDrawing()
421451 m_leftPointer = nullptr ;
422452 m_rightPointer = nullptr ;
423453 m_alwaysPointer = nullptr ;
424- m_spotlightPointer = nullptr ;
454+ if (m_overlay)
455+ {
456+ m_overlay.IsVisible (false );
457+ }
425458 ShowWindow (m_hwnd, SW_HIDE);
426459 UnhookWindowsHookEx (m_mouseHook);
427460 ClearDrawing ();
@@ -452,6 +485,16 @@ void Highlighter::ApplySettings(MouseHighlighterSettings settings)
452485 m_rightPointerEnabled = false ;
453486 }
454487
488+ // Keep spotlight overlay color updated
489+ if (m_spotlightSource)
490+ {
491+ m_spotlightSource.Color (m_alwaysColor);
492+ }
493+ if (!m_spotlightMode && m_overlay)
494+ {
495+ m_overlay.IsVisible (false );
496+ }
497+
455498 if (instance->m_visible )
456499 {
457500 instance->StopDrawing ();
@@ -563,6 +606,43 @@ void Highlighter::Terminate()
563606 }
564607}
565608
609+ float Highlighter::GetDpiScale () const
610+ {
611+ return static_cast <float >(GetDpiForWindow (m_hwnd)) / 96 .0f ;
612+ }
613+
614+ // Update spotlight radial mask center/radius with DPI-aware feather
615+ void Highlighter::UpdateSpotlightMask (float cx, float cy, float radius, bool show)
616+ {
617+ if (!m_spotlightMaskGradient)
618+ {
619+ return ;
620+ }
621+
622+ m_spotlightMaskGradient.EllipseCenter ({ cx, cy });
623+ m_spotlightMaskGradient.EllipseRadius ({ radius, radius });
624+
625+ const float dpiScale = GetDpiScale ();
626+ // Target a very fine edge: ~1 physical pixel, convert to DIPs: 1 / dpiScale
627+ const float featherDip = 1 .0f / (dpiScale > 0 .0f ? dpiScale : 1 .0f );
628+ const float safeRadius = (std::max)(radius, 1 .0f );
629+ const float featherRel = (std::min)(0 .25f , featherDip / safeRadius);
630+
631+ if (m_maskStopInner)
632+ {
633+ m_maskStopInner.Offset ((std::max)(0 .0f , 1 .0f - featherRel));
634+ }
635+
636+ if (m_spotlightSource)
637+ {
638+ m_spotlightSource.Color (m_alwaysColor);
639+ }
640+ if (m_overlay)
641+ {
642+ m_overlay.IsVisible (show);
643+ }
644+ }
645+
566646#pragma region MouseHighlighter_API
567647
568648void MouseHighlighterApplySettings (MouseHighlighterSettings settings)
0 commit comments