From a12a6285f54a02309da930c7de0682c2d44cf243 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 1 Apr 2020 11:58:16 -0500 Subject: [PATCH] Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine. --- .../spell-check/whitelist/whitelist.txt | 3 + src/cascadia/TerminalApp/lib/pch.h | 5 + src/cascadia/TerminalConnection/pch.h | 7 +- .../TerminalControl/IMouseWheelListener.idl | 16 +++ src/cascadia/TerminalControl/TermControl.cpp | 80 ++++++++++--- src/cascadia/TerminalControl/TermControl.h | 5 +- src/cascadia/TerminalControl/TermControl.idl | 4 +- .../TerminalControl/TerminalControl.vcxproj | 1 + src/cascadia/TerminalControl/pch.h | 5 + .../TerminalCore/ControlKeyStates.hpp | 21 ++++ src/cascadia/WindowsTerminal/AppHost.cpp | 47 +++++++- src/cascadia/WindowsTerminal/AppHost.h | 1 + src/cascadia/WindowsTerminal/IslandWindow.cpp | 34 ++++++ src/cascadia/WindowsTerminal/IslandWindow.h | 1 + src/cascadia/WindowsTerminal/pch.h | 4 + src/inc/til/point.h | 36 ++++++ src/til/ut_til/PointTests.cpp | 106 ++++++++++++++++++ 17 files changed, 356 insertions(+), 20 deletions(-) create mode 100644 src/cascadia/TerminalControl/IMouseWheelListener.idl diff --git a/.github/actions/spell-check/whitelist/whitelist.txt b/.github/actions/spell-check/whitelist/whitelist.txt index 6f102a7b72f..69676767846 100644 --- a/.github/actions/spell-check/whitelist/whitelist.txt +++ b/.github/actions/spell-check/whitelist/whitelist.txt @@ -1134,6 +1134,7 @@ IIo IList ime Imm +IMouse Impl implementingtextandtextrange inbox @@ -2423,6 +2424,7 @@ TOPDOWNDIB TOPLEFT TOPRIGHT tosign +touchpad tounicodeex towlower towupper @@ -2434,6 +2436,7 @@ tracelogging traceloggingprovider trackbar TRACKCOMPOSITION +trackpad trackpads transcoder transitioning diff --git a/src/cascadia/TerminalApp/lib/pch.h b/src/cascadia/TerminalApp/lib/pch.h index 5fab170bda3..3c0ef0a73a1 100644 --- a/src/cascadia/TerminalApp/lib/pch.h +++ b/src/cascadia/TerminalApp/lib/pch.h @@ -9,6 +9,8 @@ #define WIN32_LEAN_AND_MEAN +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#define BLOCK_TIL #include // This is inexplicable, but for whatever reason, cppwinrt conflicts with the // SDK definition of this function, so the only fix is to undef it. @@ -63,3 +65,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider); #include #include + +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#include "til.h" diff --git a/src/cascadia/TerminalConnection/pch.h b/src/cascadia/TerminalConnection/pch.h index 38ababb5a83..1550c51ca4e 100644 --- a/src/cascadia/TerminalConnection/pch.h +++ b/src/cascadia/TerminalConnection/pch.h @@ -12,14 +12,15 @@ #define BLOCK_GSL +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#define BLOCK_TIL #include // Must be included before any WinRT headers. #include - +#include #include -#include "winrt/Windows.Foundation.h" #include "winrt/Windows.Security.Credentials.h" #include "winrt/Windows.Foundation.Collections.h" #include @@ -27,3 +28,5 @@ #include TRACELOGGING_DECLARE_PROVIDER(g_hTerminalConnectionProvider); #include + +#include "til.h" diff --git a/src/cascadia/TerminalControl/IMouseWheelListener.idl b/src/cascadia/TerminalControl/IMouseWheelListener.idl new file mode 100644 index 00000000000..63089552683 --- /dev/null +++ b/src/cascadia/TerminalControl/IMouseWheelListener.idl @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.TerminalControl +{ + + // This interface is a hack for GH#979. Controls should implement this + // interface to be able to be notified of mousewheel events even on devices + // who's trackpads won't scroll inactive windows. + + [uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")] + interface IMouseWheelListener + { + Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta); + } +} diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 7d5d0d76ee6..9debfc28a2a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1172,6 +1172,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - Event handler for the PointerWheelChanged event. This is raised in // response to mouse wheel changes. Depending upon what modifier keys are // pressed, different actions will take place. + // - Primarily just takes the data from the PointerRoutedEventArgs and uses + // it to call _DoMouseWheel, see _DoMouseWheel for more details. // Arguments: // - args: the event args containing information about t`he mouse wheel event. void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/, @@ -1183,22 +1185,49 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } const auto point = args.GetCurrentPoint(*this); - - if (_CanSendVTMouseInput()) + auto result = _DoMouseWheel(point.Position(), + ControlKeyStates{ args.KeyModifiers() }, + point.Properties().MouseWheelDelta(), + point.Properties().IsLeftButtonPressed()); + if (result) { - _TrySendMouseEvent(point); args.Handled(true); - return; } + } - const auto delta = point.Properties().MouseWheelDelta(); + // Method Description: + // - Actually handle a scrolling event, whether from a mouse wheel or a + + // touchpad scroll. Depending upon what modifier keys are pressed, + // different actions will take place. + // * Attempts to first dispatch the mouse scroll as a VT event + // * If Ctrl+Shift are pressed, then attempts to change our opacity + // * If just Ctrl is pressed, we'll attempt to "zoom" by changing our font size + // * Otherwise, just scrolls the content of the viewport + // Arguments: + // - point: the location of the mouse during this event + // - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers + // - delta: the mouse wheel delta that triggered this event. + bool TermControl::_DoMouseWheel(const Windows::Foundation::Point point, + const ControlKeyStates modifiers, + const int32_t delta, + const bool isLeftButtonPressed) + { + if (_CanSendVTMouseInput()) + { + // Most mouse event handlers call + // _TrySendMouseEvent(point); + // here with a PointerPoint. However, as of #979, we don't have a + // PointerPoint to work with. So, we're just going to do a + // mousewheel event manually + return _terminal->SendMouseEvent(_GetTerminalPosition(point), + WM_MOUSEWHEEL, + _GetPressedModifierKeys(), + ::base::saturated_cast(delta)); + } - // Get the state of the Ctrl & Shift keys - // static_cast to a uint32_t because we can't use the WI_IsFlagSet macro - // directly with a VirtualKeyModifiers - const auto modifiers = static_cast(args.KeyModifiers()); - const auto ctrlPressed = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Control)); - const auto shiftPressed = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); + const auto ctrlPressed = modifiers.IsCtrlPressed(); + const auto shiftPressed = modifiers.IsShiftPressed(); if (ctrlPressed && shiftPressed) { @@ -1210,8 +1239,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } else { - _MouseScrollHandler(delta, point); + _MouseScrollHandler(delta, point, isLeftButtonPressed); } + return false; + } + + // Method Description: + // - This is part of the solution to GH#979 + // - Manually handle a scrolling event. This is used to help support + // scrolling on devices where the touchpad doesn't correctly handle + // scrolling inactive windows. + // Arguments: + // - location: the location of the mouse during this event. This location is + // relative to the origin of the control + // - delta: the mouse wheel delta that triggered this event. + bool TermControl::OnMouseWheel(const Windows::Foundation::Point location, + const int32_t delta) + { + const auto modifiers = _GetPressedModifierKeys(); + return _DoMouseWheel(location, modifiers, delta, false); } // Method Description: @@ -1284,7 +1330,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - Scroll the visible viewport in response to a mouse wheel event. // Arguments: // - mouseDelta: the mouse wheel delta that triggered this event. - void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint) + // - point: the location of the mouse during this event + // - isLeftButtonPressed: true iff the left mouse button was pressed during this event. + void TermControl::_MouseScrollHandler(const double mouseDelta, + const Windows::Foundation::Point point, + const bool isLeftButtonPressed) { const auto currentOffset = ScrollBar().Value(); @@ -1301,11 +1351,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // for us. ScrollBar().Value(newValue); - if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed()) + if (_terminal->IsSelectionActive() && isLeftButtonPressed) { // If user is mouse selecting and scrolls, they then point at new character. // Make sure selection reflects that immediately. - _SetEndSelectionPointAtCursor(pointerPoint.Position()); + _SetEndSelectionPointAtCursor(point); } } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index c0cf91863d7..cb28cd78abc 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -84,6 +84,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation bool OnF7Pressed(); + bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta); + ~TermControl(); Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); @@ -198,9 +200,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation winrt::fire_and_forget _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize); winrt::fire_and_forget _TerminalCursorPositionChanged(); - void _MouseScrollHandler(const double delta, Windows::UI::Input::PointerPoint const& pointerPoint); + void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed); void _MouseZoomHandler(const double delta); void _MouseTransparencyHandler(const double delta); + bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const bool isLeftButtonPressed); bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index a005ac16d5a..1a24a136011 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "IMouseWheelListener.idl"; + namespace Microsoft.Terminal.TerminalControl { delegate void TitleChangedEventArgs(String newTitle); @@ -30,7 +32,7 @@ namespace Microsoft.Terminal.TerminalControl void HandleClipboardData(String data); } - [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IF7Listener + [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IF7Listener, IMouseWheelListener { TermControl(); TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index d1691bd267f..1a664a08983 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -76,6 +76,7 @@ TermControl.xaml + TSFInputControl.xaml diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h index e14a5d46f3f..340baf2a6a4 100644 --- a/src/cascadia/TerminalControl/pch.h +++ b/src/cascadia/TerminalControl/pch.h @@ -9,6 +9,8 @@ #define WIN32_LEAN_AND_MEAN +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#define BLOCK_TIL #include // This is inexplicable, but for whatever reason, cppwinrt conflicts with the // SDK definition of this function, so the only fix is to undef it. @@ -23,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -46,3 +49,5 @@ #include TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider); #include + +#include "til.h" diff --git a/src/cascadia/TerminalCore/ControlKeyStates.hpp b/src/cascadia/TerminalCore/ControlKeyStates.hpp index cdd9c2f260d..16d6daa02cb 100644 --- a/src/cascadia/TerminalCore/ControlKeyStates.hpp +++ b/src/cascadia/TerminalCore/ControlKeyStates.hpp @@ -47,6 +47,27 @@ class Microsoft::Terminal::Core::ControlKeyStates return *this; } +#ifdef WINRT_Windows_System_H + ControlKeyStates(const winrt::Windows::System::VirtualKeyModifiers& modifiers) noexcept : + _value{ 0 } + { + // static_cast to a uint32_t because we can't use the WI_IsFlagSet + // macro directly with a VirtualKeyModifiers + const auto m = static_cast(modifiers); + _value |= WI_IsFlagSet(m, static_cast(winrt::Windows::System::VirtualKeyModifiers::Shift)) ? + SHIFT_PRESSED : + 0; + + // Since we can't differentiate between the left & right versions of Ctrl & Alt in a VirtualKeyModifiers + _value |= WI_IsFlagSet(m, static_cast(winrt::Windows::System::VirtualKeyModifiers::Menu)) ? + LEFT_ALT_PRESSED : + 0; + _value |= WI_IsFlagSet(m, static_cast(winrt::Windows::System::VirtualKeyModifiers::Control)) ? + LEFT_CTRL_PRESSED : + 0; + } +#endif + constexpr DWORD Value() const noexcept { return _value; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 2959b22220e..adf042ba22f 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -51,7 +51,7 @@ AppHost::AppHost() noexcept : _logic, std::placeholders::_1, std::placeholders::_2)); - + _window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled }); _window->MakeWindow(); } @@ -338,3 +338,48 @@ void AppHost::_ToggleFullscreen(const winrt::Windows::Foundation::IInspectable&, { _window->ToggleFullscreen(); } + +// Method Description: +// - Called when the IslandWindow has received a WM_MOUSEWHEEL message. This can +// happen on some laptops, where their trackpads won't scroll inactive windows +// _ever_. +// - We're going to take that message and manually plumb it through to our +// TermControl's, or anything else that implements IMouseWheelListener. +// - See GH#979 for more details. +// Arguments: +// - coord: The Window-relative, logical coordinates location of the mouse during this event. +// - delta: the wheel delta that triggered this event. +// Return Value: +// - +void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta) +{ + if (_logic) + { + // Find all the elements that are underneath the mouse + auto elems = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::FindElementsInHostCoordinates(coord, _logic.GetRoot()); + for (const auto& e : elems) + { + // If that element has implemented IMouseWheelListener, call OnMouseWheel on that element. + if (auto control{ e.try_as() }) + { + try + { + // Translate the event to the coordinate space of the control + // we're attempting to dispatch it to + const auto transform = e.TransformToVisual(nullptr); + const til::point controlOrigin{ til::math::flooring, transform.TransformPoint(til::point{ 0, 0 }) }; + + const til::point offsetPoint = coord - controlOrigin; + + if (control.OnMouseWheel(offsetPoint, delta)) + { + // If the element handled the mouse wheel event, don't + // continue to iterate over the remaining controls. + break; + } + } + CATCH_LOG(); + } + } + } +} diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 90b65b21c24..67f64b6e629 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -35,4 +35,5 @@ class AppHost const winrt::Windows::UI::Xaml::ElementTheme& arg); void _ToggleFullscreen(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::ToggleFullscreenEventArgs& arg); + void _WindowMouseWheeled(const til::point coord, const int32_t delta); }; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 0584711c7a7..69d5cf5bc94 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -326,6 +326,40 @@ void IslandWindow::OnSize(const UINT width, const UINT height) _windowCloseButtonClickedHandler(); return 0; } + case WM_MOUSEWHEEL: + try + { + // This whole handler is a hack for GH#979. + // + // On some laptops, their trackpads won't scroll inactive windows + // _ever_. With our entire window just being one giant XAML Island, the + // touchpad driver thinks our entire window is inactive, and won't + // scroll the XAML island. On those types of laptops, we'll get a + // WM_MOUSEWHEEL here, in our root window, when the trackpad scrolls. + // We're going to take that message and manually plumb it through to our + // TermControl's, or anything else that implements IMouseWheelListener. + + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx + // Important! Do not use the LOWORD or HIWORD macros to extract the x- + // and y- coordinates of the cursor position because these macros return + // incorrect results on systems with multiple monitors. Systems with + // multiple monitors can have negative x- and y- coordinates, and LOWORD + // and HIWORD treat the coordinates as unsigned quantities. + const til::point eventPoint{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + // This mouse event is relative to the display origin, not the window. Convert here. + const til::rectangle windowRect{ GetWindowRect() }; + const auto origin = windowRect.origin(); + const auto relative = eventPoint - origin; + // Convert to logical scaling before raising the event. + const auto real = relative / GetCurrentDpiScale(); + + const short wheelDelta = static_cast(HIWORD(wparam)); + + // Raise an event, so any listeners can handle the mouse wheel event manually. + _MouseScrolledHandlers(real, wheelDelta); + return 0; + } + CATCH_LOG(); } // TODO: handle messages here... diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 432b78c3142..4e3c96aeb14 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -72,6 +72,7 @@ class IslandWindow : DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); + WINRT_CALLBACK(MouseScrolled, winrt::delegate); protected: void ForceResize() diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index 275ec9a9239..1062d1bf7cb 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -30,6 +30,8 @@ Module Name: #include #include +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#define BLOCK_TIL #include "../inc/LibraryIncludes.h" // This is inexplicable, but for whatever reason, cppwinrt conflicts with the @@ -67,3 +69,5 @@ TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider); // For commandline argument processing #include #include + +#include "til.h" diff --git a/src/inc/til/point.h b/src/inc/til/point.h index 81691d7b641..57be2448c8f 100644 --- a/src/inc/til/point.h +++ b/src/inc/til/point.h @@ -180,6 +180,32 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" return *this; } + template + point operator*(const T& scale) const + { + static_assert(std::is_arithmetic::value, "Type must be arithmetic"); + ptrdiff_t x; + THROW_HR_IF(E_ABORT, !base::CheckMul(_x, scale).AssignIfValid(&x)); + + ptrdiff_t y; + THROW_HR_IF(E_ABORT, !base::CheckMul(_y, scale).AssignIfValid(&y)); + + return point{ x, y }; + } + + template + point operator/(const T& scale) const + { + static_assert(std::is_arithmetic::value, "Type must be arithmetic"); + ptrdiff_t x; + THROW_HR_IF(E_ABORT, !base::CheckDiv(_x, scale).AssignIfValid(&x)); + + ptrdiff_t y; + THROW_HR_IF(E_ABORT, !base::CheckDiv(_y, scale).AssignIfValid(&y)); + + return point{ x, y }; + } + constexpr ptrdiff_t x() const noexcept { return _x; @@ -233,6 +259,16 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } #endif +#ifdef WINRT_Windows_Foundation_H + operator winrt::Windows::Foundation::Point() const + { + winrt::Windows::Foundation::Point ret; + THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_x).AssignIfValid(&ret.X)); + THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_y).AssignIfValid(&ret.Y)); + return ret; + } +#endif + std::wstring to_string() const { return wil::str_printf(L"(X:%td, Y:%td)", x(), y()); diff --git a/src/til/ut_til/PointTests.cpp b/src/til/ut_til/PointTests.cpp index 8d57e5c32c8..ffb9702944c 100644 --- a/src/til/ut_til/PointTests.cpp +++ b/src/til/ut_til/PointTests.cpp @@ -605,6 +605,112 @@ class PointTests // All ptrdiff_ts fit into a float, so there's no exception tests. } + TEST_METHOD(Scaling) + { + Log::Comment(L"0.) Multiplication of two things that should be in bounds."); + { + const til::point pt{ 5, 10 }; + const int scale = 23; + + const til::point expected{ pt.x() * scale, pt.y() * scale }; + + VERIFY_ARE_EQUAL(expected, pt * scale); + } + + Log::Comment(L"1.) Multiplication results in value that is too large (x)."); + { + constexpr ptrdiff_t bigSize = std::numeric_limits().max(); + const til::point pt{ bigSize, static_cast(0) }; + const int scale = 10; + + auto fn = [&]() { + pt* scale; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"2.) Multiplication results in value that is too large (y)."); + { + constexpr ptrdiff_t bigSize = std::numeric_limits().max(); + const til::point pt{ static_cast(0), bigSize }; + const int scale = 10; + + auto fn = [&]() { + pt* scale; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"3.) Division of two things that should be in bounds."); + { + const til::point pt{ 555, 510 }; + const int scale = 23; + + const til::point expected{ pt.x() / scale, pt.y() / scale }; + + VERIFY_ARE_EQUAL(expected, pt / scale); + } + + Log::Comment(L"4.) Division by zero"); + { + constexpr ptrdiff_t bigSize = std::numeric_limits().max(); + const til::point pt{ 1, 1 }; + const int scale = 0; + + auto fn = [&]() { + pt / scale; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"5.) Multiplication of floats that should be in bounds."); + { + const til::point pt{ 3, 10 }; + const float scale = 5.5f; + + // 3 * 5.5 = 15.5, which we'll round to 15 + const til::point expected{ 16, 55 }; + + VERIFY_ARE_EQUAL(expected, pt * scale); + } + + Log::Comment(L"6.) Multiplication of doubles that should be in bounds."); + { + const til::point pt{ 3, 10 }; + const double scale = 5.5f; + + // 3 * 5.5 = 15.5, which we'll round to 15 + const til::point expected{ 16, 55 }; + + VERIFY_ARE_EQUAL(expected, pt * scale); + } + + Log::Comment(L"5.) Division of floats that should be in bounds."); + { + const til::point pt{ 15, 10 }; + const float scale = 2.0f; + + // 15 / 2 = 7.5, which we'll floor to 7 + const til::point expected{ 7, 5 }; + + VERIFY_ARE_EQUAL(expected, pt / scale); + } + + Log::Comment(L"6.) Division of doubles that should be in bounds."); + { + const til::point pt{ 15, 10 }; + const double scale = 2.0; + + // 15 / 2 = 7.5, which we'll floor to 7 + const til::point expected{ 7, 5 }; + + VERIFY_ARE_EQUAL(expected, pt / scale); + } + } + template struct PointTypeWith_xy {