diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ff7a8fdf02..ab3e43eb47 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1810,7 +1810,6 @@ TILEDWINDOW TILLSON timedate timediff -timeunion timeutil TITLEBARINFO Titlecase diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 5221e12a63..1c408175a0 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -77,10 +77,8 @@ protected: int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS; int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM; DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS; - int m_finalAlphaNumerator = 100; // legacy (root now always animates to 1.0; kept for GDI fallback compatibility) std::vector m_excludedApps; int m_shakeMinimumDistance = FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE; - static constexpr int FinalAlphaDenominator = 100; winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{ nullptr }; // Don't consider movements started past these milliseconds to detect shaking. @@ -816,13 +814,16 @@ private: // Dim color (source) m_dimColorBrush = m_compositor.CreateColorBrush(m_backgroundColor); // Radial gradient mask (center transparent, outer opaque) + // Fixed feather width: 1px for radius < 300, 2px for radius >= 300 + const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f; + const float featherOffset = 1.0f - featherPixels / (rDip * zoom); m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush(); m_spotlightMaskGradient.MappingMode(muxc::CompositionMappingMode::Absolute); m_maskStopCenter = m_compositor.CreateColorGradientStop(); m_maskStopCenter.Offset(0.0f); m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0)); m_maskStopInner = m_compositor.CreateColorGradientStop(); - m_maskStopInner.Offset(0.995f); + m_maskStopInner.Offset(featherOffset); m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0)); m_maskStopOuter = m_compositor.CreateColorGradientStop(); m_maskStopOuter.Offset(1.0f); @@ -852,23 +853,7 @@ private: m_root.ImplicitAnimations(collection); // 6) Spotlight radius shrinks as opacity increases (expression animation) - auto radiusExpression = m_compositor.CreateExpressionAnimation(); - radiusExpression.SetReferenceParameter(L"Root", m_root); - - wchar_t expressionText[256]; - winrt::check_hresult(StringCchPrintfW( - expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius)); - - radiusExpression.Expression(expressionText); - m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression); - // Also animate spotlight geometry radius for visual consistency - if (m_circleGeometry) - { - auto radiusExpression2 = m_compositor.CreateExpressionAnimation(); - radiusExpression2.SetReferenceParameter(L"Root", m_root); - radiusExpression2.Expression(expressionText); - m_circleGeometry.StartAnimation(L"Radius", radiusExpression2); - } + SetupRadiusAnimations(rDip * zoom, rDip, featherPixels); // Composition created successfully return true; @@ -887,6 +872,41 @@ private: } } + // Helper to setup radius and feather expression animations + void SetupRadiusAnimations(float startRadiusDip, float endRadiusDip, float featherPixels) + { + // Radius expression: shrinks from startRadiusDip to endRadiusDip as opacity goes 0->1 + auto radiusExpression = m_compositor.CreateExpressionAnimation(); + radiusExpression.SetReferenceParameter(L"Root", m_root); + wchar_t expressionText[256]; + winrt::check_hresult(StringCchPrintfW( + expressionText, ARRAYSIZE(expressionText), + L"Lerp(Vector2(%.1f, %.1f), Vector2(%.1f, %.1f), Root.Opacity)", + startRadiusDip, startRadiusDip, endRadiusDip, endRadiusDip)); + radiusExpression.Expression(expressionText); + m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression); + + // Feather expression: maintains fixed pixel width as radius changes + auto featherExpression = m_compositor.CreateExpressionAnimation(); + featherExpression.SetReferenceParameter(L"Root", m_root); + wchar_t featherExpressionText[256]; + winrt::check_hresult(StringCchPrintfW( + featherExpressionText, ARRAYSIZE(featherExpressionText), + L"1.0f - %.1ff / Lerp(%.1ff, %.1ff, Root.Opacity)", + featherPixels, startRadiusDip, endRadiusDip)); + featherExpression.Expression(featherExpressionText); + m_maskStopInner.StartAnimation(L"Offset", featherExpression); + + // Circle geometry radius for visual consistency + if (m_circleGeometry) + { + auto radiusExpression2 = m_compositor.CreateExpressionAnimation(); + radiusExpression2.SetReferenceParameter(L"Root", m_root); + radiusExpression2.Expression(expressionText); + m_circleGeometry.StartAnimation(L"Radius", radiusExpression2); + } + } + void UpdateIslandSize() { if (!m_island) @@ -964,27 +984,21 @@ public: const float scale = static_cast(m_surface.XamlRoot().RasterizationScale()); const float rDip = m_sonarRadiusFloat / scale; const float zoom = static_cast(m_sonarZoomFactor); + const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f; + const float startRadiusDip = rDip * zoom; m_spotlightMaskGradient.StopAnimation(L"EllipseRadius"); - m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom }); - if (m_spotlight) - { - m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom }); - m_circleShape.Offset({ rDip * zoom, rDip * zoom }); - } - auto radiusExpression = m_compositor.CreateExpressionAnimation(); - radiusExpression.SetReferenceParameter(L"Root", m_root); - wchar_t expressionText[256]; - winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius)); - radiusExpression.Expression(expressionText); - m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression); + m_maskStopInner.StopAnimation(L"Offset"); if (m_circleGeometry) { m_circleGeometry.StopAnimation(L"Radius"); - auto radiusExpression2 = m_compositor.CreateExpressionAnimation(); - radiusExpression2.SetReferenceParameter(L"Root", m_root); - radiusExpression2.Expression(expressionText); - m_circleGeometry.StartAnimation(L"Radius", radiusExpression2); } + m_spotlightMaskGradient.EllipseCenter({ startRadiusDip, startRadiusDip }); + if (m_spotlight) + { + m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom }); + m_circleShape.Offset({ startRadiusDip, startRadiusDip }); + } + SetupRadiusAnimations(startRadiusDip, rDip, featherPixels); } }); if (!enqueueSucceeded) @@ -1018,202 +1032,6 @@ private: muxc::ScalarKeyFrameAnimation m_animation{ nullptr }; }; -template -struct GdiSonar : SuperSonar -{ - LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept - { - switch (message) - { - case WM_CREATE: - SetLayeredWindowAttributes(this->m_hwnd, 0, 0, LWA_ALPHA); - break; - - case WM_TIMER: - switch (wParam) - { - case TIMER_ID_FADE: - OnFadeTimer(); - break; - } - break; - - case WM_PAINT: - this->Shim()->OnPaint(); - break; - } - return this->BaseWndProc(message, wParam, lParam); - } - - void BeforeMoveSonar() { this->Shim()->InvalidateSonar(); } - void AfterMoveSonar() { this->Shim()->InvalidateSonar(); } - - void SetSonarVisibility(bool visible) - { - m_alphaTarget = visible ? MaxAlpha : 0; - m_fadeStart = GetTickCount() - FadeFramePeriod; - SetTimer(this->m_hwnd, TIMER_ID_FADE, FadeFramePeriod, nullptr); - OnFadeTimer(); - } - - void OnFadeTimer() - { - auto now = GetTickCount(); - auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration); - - this->Shim()->InvalidateSonar(); - if (m_alpha < m_alphaTarget) - { - m_alpha += step; - if (m_alpha > m_alphaTarget) - m_alpha = m_alphaTarget; - } - else if (m_alpha > m_alphaTarget) - { - m_alpha -= step; - if (m_alpha < m_alphaTarget) - m_alpha = m_alphaTarget; - } - SetLayeredWindowAttributes(this->m_hwnd, 0, (BYTE)m_alpha, LWA_ALPHA); - this->Shim()->InvalidateSonar(); - if (m_alpha == m_alphaTarget) - { - KillTimer(this->m_hwnd, TIMER_ID_FADE); - if (m_alpha == 0) - { - ShowWindow(this->m_hwnd, SW_HIDE); - } - } - else - { - ShowWindow(this->m_hwnd, SW_SHOWNOACTIVATE); - } - } - -protected: - int CurrentSonarRadius() - { - int range = MaxAlpha - m_alpha; - int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha; - return radius; - } - -private: - static constexpr DWORD FadeFramePeriod = 10; - int MaxAlpha = SuperSonar::m_finalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; - static constexpr DWORD TIMER_ID_FADE = 101; - -private: - int m_alpha = 0; - int m_alphaTarget = 0; - DWORD m_fadeStart = 0; -}; - -struct GdiSpotlight : GdiSonar -{ - void InvalidateSonar() - { - RECT rc; - auto radius = CurrentSonarRadius(); - rc.left = this->m_sonarPos.x - radius; - rc.top = this->m_sonarPos.y - radius; - rc.right = this->m_sonarPos.x + radius; - rc.bottom = this->m_sonarPos.y + radius; - InvalidateRect(this->m_hwnd, &rc, FALSE); - } - - void OnPaint() - { - PAINTSTRUCT ps; - BeginPaint(this->m_hwnd, &ps); - - auto radius = CurrentSonarRadius(); - auto spotlight = CreateRoundRectRgn( - this->m_sonarPos.x - radius, this->m_sonarPos.y - radius, this->m_sonarPos.x + radius, this->m_sonarPos.y + radius, radius * 2, radius * 2); - - FillRgn(ps.hdc, spotlight, static_cast(GetStockObject(WHITE_BRUSH))); - Sleep(1000 / 60); - ExtSelectClipRgn(ps.hdc, spotlight, RGN_DIFF); - FillRect(ps.hdc, &ps.rcPaint, static_cast(GetStockObject(BLACK_BRUSH))); - DeleteObject(spotlight); - - EndPaint(this->m_hwnd, &ps); - } -}; - -struct GdiCrosshairs : GdiSonar -{ - void InvalidateSonar() - { - RECT rc; - auto radius = CurrentSonarRadius(); - GetClientRect(m_hwnd, &rc); - rc.left = m_sonarPos.x - radius; - rc.right = m_sonarPos.x + radius; - InvalidateRect(m_hwnd, &rc, FALSE); - - GetClientRect(m_hwnd, &rc); - rc.top = m_sonarPos.y - radius; - rc.bottom = m_sonarPos.y + radius; - InvalidateRect(m_hwnd, &rc, FALSE); - } - - void OnPaint() - { - PAINTSTRUCT ps; - BeginPaint(this->m_hwnd, &ps); - - auto radius = CurrentSonarRadius(); - RECT rc; - - HBRUSH white = static_cast(GetStockObject(WHITE_BRUSH)); - - rc.left = m_sonarPos.x - radius; - rc.top = ps.rcPaint.top; - rc.right = m_sonarPos.x + radius; - rc.bottom = ps.rcPaint.bottom; - FillRect(ps.hdc, &rc, white); - - rc.left = ps.rcPaint.left; - rc.top = m_sonarPos.y - radius; - rc.right = ps.rcPaint.right; - rc.bottom = m_sonarPos.y + radius; - FillRect(ps.hdc, &rc, white); - - HBRUSH black = static_cast(GetStockObject(BLACK_BRUSH)); - - // Top left - rc.left = ps.rcPaint.left; - rc.top = ps.rcPaint.top; - rc.right = m_sonarPos.x - radius; - rc.bottom = m_sonarPos.y - radius; - FillRect(ps.hdc, &rc, black); - - // Top right - rc.left = m_sonarPos.x + radius; - rc.top = ps.rcPaint.top; - rc.right = ps.rcPaint.right; - rc.bottom = m_sonarPos.y - radius; - FillRect(ps.hdc, &rc, black); - - // Bottom left - rc.left = ps.rcPaint.left; - rc.top = m_sonarPos.y + radius; - rc.right = m_sonarPos.x - radius; - rc.bottom = ps.rcPaint.bottom; - FillRect(ps.hdc, &rc, black); - - // Bottom right - rc.left = m_sonarPos.x + radius; - rc.top = m_sonarPos.y + radius; - rc.right = ps.rcPaint.right; - rc.bottom = ps.rcPaint.bottom; - FillRect(ps.hdc, &rc, black); - - EndPaint(this->m_hwnd, &ps); - } -}; - #pragma endregion Super_Sonar_Base_Code #pragma region Super_Sonar_API @@ -1284,4 +1102,4 @@ HWND GetSonarHwnd() noexcept return nullptr; } -#pragma endregion Super_Sonar_API +#pragma endregion Super_Sonar_API \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj index bfed4af15d..8e606b88ad 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj @@ -154,4 +154,4 @@ - \ No newline at end of file +