mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 17:56:44 +02:00
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" />|
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "MouseHighlighter.h"
|
||||
#include "trace.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef COMPOSITION
|
||||
namespace winrt
|
||||
@@ -49,6 +50,9 @@ private:
|
||||
void BringToFront();
|
||||
HHOOK m_mouseHook = NULL;
|
||||
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
// Helpers for spotlight overlay
|
||||
float GetDpiScale() const;
|
||||
void UpdateSpotlightMask(float cx, float cy, float radius, bool show);
|
||||
|
||||
static constexpr auto m_className = L"MouseHighlighter";
|
||||
static constexpr auto m_windowTitle = L"PowerToys Mouse Highlighter";
|
||||
@@ -67,7 +71,14 @@ private:
|
||||
winrt::CompositionSpriteShape m_leftPointer{ nullptr };
|
||||
winrt::CompositionSpriteShape m_rightPointer{ nullptr };
|
||||
winrt::CompositionSpriteShape m_alwaysPointer{ nullptr };
|
||||
winrt::CompositionSpriteShape m_spotlightPointer{ nullptr };
|
||||
// Spotlight overlay (mask with soft feathered edge)
|
||||
winrt::SpriteVisual m_overlay{ nullptr };
|
||||
winrt::CompositionMaskBrush m_spotlightMask{ nullptr };
|
||||
winrt::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
|
||||
winrt::CompositionColorBrush m_spotlightSource{ nullptr };
|
||||
winrt::CompositionColorGradientStop m_maskStopCenter{ nullptr };
|
||||
winrt::CompositionColorGradientStop m_maskStopInner{ nullptr };
|
||||
winrt::CompositionColorGradientStop m_maskStopOuter{ nullptr };
|
||||
|
||||
bool m_leftPointerEnabled = true;
|
||||
bool m_rightPointerEnabled = true;
|
||||
@@ -123,6 +134,35 @@ bool Highlighter::CreateHighlighter()
|
||||
m_shape.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_root.Children().InsertAtTop(m_shape);
|
||||
|
||||
// Create spotlight overlay (soft feather, DPI-aware)
|
||||
m_overlay = m_compositor.CreateSpriteVisual();
|
||||
m_overlay.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_spotlightSource = m_compositor.CreateColorBrush(m_alwaysColor);
|
||||
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
|
||||
m_spotlightMaskGradient.MappingMode(winrt::CompositionMappingMode::Absolute);
|
||||
// Center region fully transparent
|
||||
m_maskStopCenter = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopCenter.Offset(0.0f);
|
||||
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
// Inner edge of feather (still transparent)
|
||||
m_maskStopInner = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopInner.Offset(0.995f); // will be updated per-radius
|
||||
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
// Outer edge (opaque mask -> overlay visible)
|
||||
m_maskStopOuter = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopOuter.Offset(1.0f);
|
||||
m_maskStopOuter.Color(winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255));
|
||||
m_spotlightMaskGradient.ColorStops().Append(m_maskStopCenter);
|
||||
m_spotlightMaskGradient.ColorStops().Append(m_maskStopInner);
|
||||
m_spotlightMaskGradient.ColorStops().Append(m_maskStopOuter);
|
||||
|
||||
m_spotlightMask = m_compositor.CreateMaskBrush();
|
||||
m_spotlightMask.Source(m_spotlightSource);
|
||||
m_spotlightMask.Mask(m_spotlightMaskGradient);
|
||||
m_overlay.Brush(m_spotlightMask);
|
||||
m_overlay.IsVisible(false);
|
||||
m_root.Children().InsertAtTop(m_overlay);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
@@ -165,12 +205,8 @@ void Highlighter::AddDrawingPoint(MouseButton button)
|
||||
// always
|
||||
if (m_spotlightMode)
|
||||
{
|
||||
float borderThickness = static_cast<float>(std::hypot(GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN)));
|
||||
circleGeometry.Radius({ static_cast<float>(borderThickness / 2.0 + m_radius), static_cast<float>(borderThickness / 2.0 + m_radius) });
|
||||
circleShape.FillBrush(nullptr);
|
||||
circleShape.StrokeBrush(m_compositor.CreateColorBrush(m_alwaysColor));
|
||||
circleShape.StrokeThickness(borderThickness);
|
||||
m_spotlightPointer = circleShape;
|
||||
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -209,20 +245,14 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
|
||||
}
|
||||
else
|
||||
{
|
||||
// always
|
||||
// always / spotlight idle
|
||||
if (m_spotlightMode)
|
||||
{
|
||||
if (m_spotlightPointer)
|
||||
{
|
||||
m_spotlightPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
|
||||
}
|
||||
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
|
||||
}
|
||||
else
|
||||
else if (m_alwaysPointer)
|
||||
{
|
||||
if (m_alwaysPointer)
|
||||
{
|
||||
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
|
||||
}
|
||||
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,9 +296,9 @@ void Highlighter::ClearDrawingPoint()
|
||||
{
|
||||
if (m_spotlightMode)
|
||||
{
|
||||
if (m_spotlightPointer)
|
||||
if (m_overlay)
|
||||
{
|
||||
m_spotlightPointer.StrokeBrush().as<winrt::Windows::UI::Composition::CompositionColorBrush>().Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
m_overlay.IsVisible(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -421,7 +451,10 @@ void Highlighter::StopDrawing()
|
||||
m_leftPointer = nullptr;
|
||||
m_rightPointer = nullptr;
|
||||
m_alwaysPointer = nullptr;
|
||||
m_spotlightPointer = nullptr;
|
||||
if (m_overlay)
|
||||
{
|
||||
m_overlay.IsVisible(false);
|
||||
}
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
UnhookWindowsHookEx(m_mouseHook);
|
||||
ClearDrawing();
|
||||
@@ -452,6 +485,16 @@ void Highlighter::ApplySettings(MouseHighlighterSettings settings)
|
||||
m_rightPointerEnabled = false;
|
||||
}
|
||||
|
||||
// Keep spotlight overlay color updated
|
||||
if (m_spotlightSource)
|
||||
{
|
||||
m_spotlightSource.Color(m_alwaysColor);
|
||||
}
|
||||
if (!m_spotlightMode && m_overlay)
|
||||
{
|
||||
m_overlay.IsVisible(false);
|
||||
}
|
||||
|
||||
if (instance->m_visible)
|
||||
{
|
||||
instance->StopDrawing();
|
||||
@@ -563,6 +606,43 @@ void Highlighter::Terminate()
|
||||
}
|
||||
}
|
||||
|
||||
float Highlighter::GetDpiScale() const
|
||||
{
|
||||
return static_cast<float>(GetDpiForWindow(m_hwnd)) / 96.0f;
|
||||
}
|
||||
|
||||
// Update spotlight radial mask center/radius with DPI-aware feather
|
||||
void Highlighter::UpdateSpotlightMask(float cx, float cy, float radius, bool show)
|
||||
{
|
||||
if (!m_spotlightMaskGradient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_spotlightMaskGradient.EllipseCenter({ cx, cy });
|
||||
m_spotlightMaskGradient.EllipseRadius({ radius, radius });
|
||||
|
||||
const float dpiScale = GetDpiScale();
|
||||
// Target a very fine edge: ~1 physical pixel, convert to DIPs: 1 / dpiScale
|
||||
const float featherDip = 1.0f / (dpiScale > 0.0f ? dpiScale : 1.0f);
|
||||
const float safeRadius = (std::max)(radius, 1.0f);
|
||||
const float featherRel = (std::min)(0.25f, featherDip / safeRadius);
|
||||
|
||||
if (m_maskStopInner)
|
||||
{
|
||||
m_maskStopInner.Offset((std::max)(0.0f, 1.0f - featherRel));
|
||||
}
|
||||
|
||||
if (m_spotlightSource)
|
||||
{
|
||||
m_spotlightSource.Color(m_alwaysColor);
|
||||
}
|
||||
if (m_overlay)
|
||||
{
|
||||
m_overlay.IsVisible(show);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region MouseHighlighter_API
|
||||
|
||||
void MouseHighlighterApplySettings(MouseHighlighterSettings settings)
|
||||
|
||||
Reference in New Issue
Block a user