Skip to content

Commit 3682f18

Browse files
authored
Mouse highlighter spotlight mode, fix the gpu perf issue (#41079)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request The big border solution for implementing the spotlight mode is gpu consuming, switch to a resource friendly implementation. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed | Before | After | |----------|----------| | <img width="739" height="263" alt="image" src="https://github.com/user-attachments/assets/b369801a-4cda-44e8-968b-d76586931c8c" />| <img width="1031" height="319" alt="image" src="https://github.com/user-attachments/assets/9b21c96d-f5ce-4ff7-8662-0c4e6e075976" />|
1 parent 1eae1d9 commit 3682f18

File tree

1 file changed

+100
-20
lines changed

1 file changed

+100
-20
lines changed

src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "MouseHighlighter.h"
66
#include "trace.h"
77
#include <cmath>
8+
#include <algorithm>
89

910
#ifdef COMPOSITION
1011
namespace 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

568648
void MouseHighlighterApplySettings(MouseHighlighterSettings settings)

0 commit comments

Comments
 (0)