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 {