5
5
#include " MouseHighlighter.h"
6
6
#include " trace.h"
7
7
#include < cmath>
8
+ #include < algorithm>
8
9
9
10
#ifdef COMPOSITION
10
11
namespace winrt
@@ -49,6 +50,9 @@ struct Highlighter
49
50
void BringToFront ();
50
51
HHOOK m_mouseHook = NULL ;
51
52
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);
52
56
53
57
static constexpr auto m_className = L" MouseHighlighter" ;
54
58
static constexpr auto m_windowTitle = L" PowerToys Mouse Highlighter" ;
@@ -67,7 +71,14 @@ struct Highlighter
67
71
winrt::CompositionSpriteShape m_leftPointer{ nullptr };
68
72
winrt::CompositionSpriteShape m_rightPointer{ nullptr };
69
73
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 };
71
82
72
83
bool m_leftPointerEnabled = true ;
73
84
bool m_rightPointerEnabled = true ;
@@ -123,6 +134,35 @@ bool Highlighter::CreateHighlighter()
123
134
m_shape.RelativeSizeAdjustment ({ 1 .0f , 1 .0f });
124
135
m_root.Children ().InsertAtTop (m_shape);
125
136
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
+
126
166
return true ;
127
167
}
128
168
catch (...)
@@ -165,12 +205,8 @@ void Highlighter::AddDrawingPoint(MouseButton button)
165
205
// always
166
206
if (m_spotlightMode)
167
207
{
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 ;
174
210
}
175
211
else
176
212
{
@@ -209,20 +245,14 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
209
245
}
210
246
else
211
247
{
212
- // always
248
+ // always / spotlight idle
213
249
if (m_spotlightMode)
214
250
{
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 );
219
252
}
220
- else
253
+ else if (m_alwaysPointer)
221
254
{
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 ) });
226
256
}
227
257
}
228
258
}
@@ -266,9 +296,9 @@ void Highlighter::ClearDrawingPoint()
266
296
{
267
297
if (m_spotlightMode)
268
298
{
269
- if (m_spotlightPointer )
299
+ if (m_overlay )
270
300
{
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 );
272
302
}
273
303
}
274
304
else
@@ -421,7 +451,10 @@ void Highlighter::StopDrawing()
421
451
m_leftPointer = nullptr ;
422
452
m_rightPointer = nullptr ;
423
453
m_alwaysPointer = nullptr ;
424
- m_spotlightPointer = nullptr ;
454
+ if (m_overlay)
455
+ {
456
+ m_overlay.IsVisible (false );
457
+ }
425
458
ShowWindow (m_hwnd, SW_HIDE);
426
459
UnhookWindowsHookEx (m_mouseHook);
427
460
ClearDrawing ();
@@ -452,6 +485,16 @@ void Highlighter::ApplySettings(MouseHighlighterSettings settings)
452
485
m_rightPointerEnabled = false ;
453
486
}
454
487
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
+
455
498
if (instance->m_visible )
456
499
{
457
500
instance->StopDrawing ();
@@ -563,6 +606,43 @@ void Highlighter::Terminate()
563
606
}
564
607
}
565
608
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
+
566
646
#pragma region MouseHighlighter_API
567
647
568
648
void MouseHighlighterApplySettings (MouseHighlighterSettings settings)
0 commit comments