From 8acd8ee05be3b8eb74b8cdf0499e866fa9736fb7 Mon Sep 17 00:00:00 2001 From: Paul Meems Date: Fri, 6 Nov 2026 14:58:21 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20feat(activex):=20implement=20in?= =?UTF-8?q?teractive=20mouse=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pan with captured left-button drags and anchor wheel zoom at the cursor. - Test hosted input and document the navigation behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 5 +- docs/ARCHITECTURE.md | 13 +- docs/TESTING.md | 11 +- src/MapWinGIS.ActiveX/MapWinGISControl.cpp | 241 ++++++++++++++++++ src/MapWinGIS.ActiveX/MapWinGISControl.h | 18 ++ .../ActiveXActivationTests.cpp | 134 ++++++++++ 6 files changed, 413 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ce4c946..f198c3b 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ cancellation events. The core also supports a shared map viewport with pan, center zoom, anchored zoom, and fitting to the visible map or a named layer. These navigation calls do not depend on a window. `IMapWinGISControl4` exposes this programmatic -navigation and stable layer IDs through ActiveX; mouse navigation remains -future work. +navigation and stable layer IDs through ActiveX. The control also supports +left-button drag panning and cursor-anchored mouse-wheel zoom. Resizing keeps +the current map center and resolution. Vector loads add layers rather than replace them. Call `ClearLayers` to release the loaded datasets before switching or refreshing them. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0ecc67b..29142b1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -228,8 +228,9 @@ has no extent or transform of its own. the view cancels it. A missing layer or an invalid extent also cancels a pending fit. Navigation that returns `false` keeps it, including `ZoomAt` on a zero-sized viewport and center zoom at a resolution limit. - None of this depends on COM, mouse messages, or a - renderer; Phase 5 maps input onto these calls. + None of this depends on COM, mouse messages, or a renderer. The ActiveX + control maps left-button drags and cursor-anchored wheel input onto these + calls. ## ActiveX and rendering architecture @@ -261,7 +262,13 @@ viewport. For windowless hosts, `SetObjectRects` supplies the live size. output, and scaled draws do not change the live viewport or its pending fit. If no view exists yet, drawing fits only that copy to the visible map extent. Resizing updates the OpenGL viewport and staging buffers without changing -ownership of map data. +ownership of map data. A left-button drag pans the shared `MapView` by the +pixel delta converted with the viewport's map-units-per-pixel resolution. +Wheel input calls `MapView::ZoomAt` at the cursor. The control acquires and +releases mouse capture through either its window or its windowless in-place +site, and ends a drag on capture loss, cancellation, close, or destruction. +These view changes do not rebuild the render scene, and both backends draw +through the updated viewport. Rendering consumes backend-neutral input prepared from core data; neither the coordinator nor a backend loads datasets or implements GIS rules. For each diff --git a/docs/TESTING.md b/docs/TESTING.md index 0d81f9f..eadc95c 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -28,8 +28,9 @@ up automatically). The native `MapWinGIS.ActiveX.Tests` project covers COM activation, registered control rendering through GDI and OpenGL, and GDAL progress/completion events. -The registered-control host test is opt-in and runs through -`tests\MapWinGIS.ActiveX.Tests\Verify-ActiveXActivation.ps1`. +Its registered-control host test also sends a wheel zoom and captured drag to +the hosted control and verifies their viewport effects. Run this opt-in test +with `tests\MapWinGIS.ActiveX.Tests\Verify-ActiveXActivation.ps1`. The .NET 10 WinForms host exercises the real ActiveX control through `AxHost` using registration-free COM activation from its embedded application @@ -156,8 +157,8 @@ unchanged-geometry cache. Total memory includes the OpenGL driver and context. Map view tests fit point and zero-span extents, and tiny targets with default padding. Map scene tests isolate standard geometry-read exceptions, reuse the resulting scene, and let allocation failures propagate. -- Navigation is tested without input events: `ViewportTests.cpp` covers the - pan, center-zoom, and anchored-zoom calculations and their limits, and +- `ViewportTests.cpp` covers pan, center-zoom, and anchored-zoom calculations + and their limits, and `MapViewTests.cpp` covers zoom to map and layer, zoom in and out, empty and missing-layer cases, and pending-fit handling on `MapView`. Pending-fit tests cover replacement in both directions between map and layer fits, @@ -166,6 +167,8 @@ unchanged-geometry cache. Total memory includes the OpenGL driver and context. `ZoomAt` on a zero-sized viewport, which keep the pending fit. Viewport tests cover anchor movement when the new center raises the minimum resolution, and direct zoom-out multiplication without a reciprocal. + The opt-in registered-control host test also verifies cursor-anchored wheel + zoom and drag-to-pan pixel scaling and direction. - Cover for each feature: happy path, empty/missing input, out-of-range indexes, null values, non-ASCII paths, write-then-reopen round-trips, and error cases. diff --git a/src/MapWinGIS.ActiveX/MapWinGISControl.cpp b/src/MapWinGIS.ActiveX/MapWinGISControl.cpp index cfa9d33..9ae5ba5 100644 --- a/src/MapWinGIS.ActiveX/MapWinGISControl.cpp +++ b/src/MapWinGIS.ActiveX/MapWinGISControl.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,8 @@ #include #include +#include + #include "ComConversion.h" #include "mapwingis/core/Error.h" #include "mapwingis/gdal/GdalEnvironment.h" @@ -31,6 +34,8 @@ namespace { + constexpr double kMouseWheelZoomFactor = 1.2; + using mwg::activex::ComConversion::BstrToUtf8; using mwg::activex::ComConversion::CopyStringArray; using mwg::activex::ComConversion::CopyToBstr; @@ -706,6 +711,239 @@ bool CMapWinGISControl::ResizeView(const LONG width, const LONG height) noexcept } } +bool CMapWinGISControl::CaptureMouse() noexcept +{ + if (m_bWndLess) + { + return m_spInPlaceSite != nullptr && + SUCCEEDED(m_spInPlaceSite->SetCapture(TRUE)); + } + + if (m_hWnd == nullptr) + { + return false; + } + + ::SetCapture(m_hWnd); + return ::GetCapture() == m_hWnd; +} + +void CMapWinGISControl::ReleaseMouseCapture() noexcept +{ + if (m_bWndLess) + { + if (m_spInPlaceSite != nullptr) + { + m_spInPlaceSite->SetCapture(FALSE); + } + } + else if (m_hWnd != nullptr && ::GetCapture() == m_hWnd) + { + ::ReleaseCapture(); + } +} + +void CMapWinGISControl::CancelMousePan() noexcept +{ + if (_isPanning) + { + _isPanning = false; + ReleaseMouseCapture(); + } +} + +bool CMapWinGISControl::GetMousePoint( + const LPARAM lParam, + const bool screenCoordinates, + POINT& point) const noexcept +{ + point = POINT{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + + HWND coordinateWindow = m_hWnd; + if (screenCoordinates) + { + if (m_bWndLess) + { + if (m_spInPlaceSite == nullptr || + FAILED(m_spInPlaceSite->GetWindow(&coordinateWindow))) + { + return false; + } + } + + if (coordinateWindow == nullptr || + !::ScreenToClient(coordinateWindow, &point)) + { + return false; + } + } + + if (m_bWndLess) + { + point.x -= m_rcPos.left; + point.y -= m_rcPos.top; + } + return true; +} + +LRESULT CMapWinGISControl::OnLButtonDown( + UINT, + WPARAM, + const LPARAM lParam, + BOOL& handled) +{ + POINT point{}; + if (!GetMousePoint(lParam, false, point) || !CaptureMouse()) + { + handled = FALSE; + return 0; + } + + _lastPanPoint = point; + _isPanning = true; + handled = TRUE; + return 0; +} + +LRESULT CMapWinGISControl::OnMouseMove( + UINT, + const WPARAM wParam, + const LPARAM lParam, + BOOL& handled) +{ + if (!_isPanning) + { + handled = FALSE; + return 0; + } + + if ((wParam & MK_LBUTTON) == 0) + { + CancelMousePan(); + handled = TRUE; + return 0; + } + + POINT point{}; + if (!GetMousePoint(lParam, false, point)) + { + handled = TRUE; + return 0; + } + + const LONG pixelDeltaX = point.x - _lastPanPoint.x; + const LONG pixelDeltaY = point.y - _lastPanPoint.y; + _lastPanPoint = point; + if (pixelDeltaX == 0 && pixelDeltaY == 0) + { + handled = TRUE; + return 0; + } + + const std::optional resolution = _view.GetViewport().Resolution(); + if (!resolution.has_value()) + { + handled = TRUE; + return 0; + } + + bool changed = false; + const HRESULT result = RunViewOperation( + [this, pixelDeltaX, pixelDeltaY, resolution]() + { + return _view.Pan( + -static_cast(pixelDeltaX) * *resolution, + static_cast(pixelDeltaY) * *resolution); + }, + changed); + if (SUCCEEDED(result) && changed) + { + FireViewChange(); + } + + handled = TRUE; + return 0; +} + +LRESULT CMapWinGISControl::OnLButtonUp( + UINT, + WPARAM, + LPARAM, + BOOL& handled) +{ + if (!_isPanning) + { + handled = FALSE; + return 0; + } + + CancelMousePan(); + handled = TRUE; + return 0; +} + +LRESULT CMapWinGISControl::OnMouseWheel( + UINT, + const WPARAM wParam, + const LPARAM lParam, + BOOL& handled) +{ + const int wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); + POINT point{}; + const mwg::Viewport& viewport = _view.GetViewport(); + if (wheelDelta == 0 || + !viewport.IsValid() || + !GetMousePoint(lParam, true, point) || + point.x < 0 || point.x >= viewport.Width() || + point.y < 0 || point.y >= viewport.Height()) + { + handled = FALSE; + return 0; + } + + const double factor = std::pow( + kMouseWheelZoomFactor, + static_cast(wheelDelta) / WHEEL_DELTA); + bool changed = false; + const HRESULT result = RunViewOperation( + [this, point, factor]() + { + return _view.ZoomAt( + {static_cast(point.x), static_cast(point.y)}, + factor); + }, + changed); + if (SUCCEEDED(result) && changed) + { + FireViewChange(); + } + + handled = TRUE; + return 0; +} + +LRESULT CMapWinGISControl::OnCaptureChanged( + UINT, + WPARAM, + LPARAM, + BOOL& handled) +{ + _isPanning = false; + handled = TRUE; + return 0; +} + +LRESULT CMapWinGISControl::OnCancelMode( + UINT, + WPARAM, + LPARAM, + BOOL& handled) +{ + CancelMousePan(); + handled = TRUE; + return 0; +} + LRESULT CMapWinGISControl::OnSize( UINT, WPARAM, @@ -736,6 +974,7 @@ LRESULT CMapWinGISControl::OnDestroy( LPARAM, BOOL& handled) { + CancelMousePan(); _gdalEvents.OnDestroy(); handled = FALSE; return 0; @@ -762,6 +1001,7 @@ void CMapWinGISControl::DispatchGdalNotifications(void* const control) noexcept STDMETHODIMP CMapWinGISControl::Close(const DWORD saveOption) { + CancelMousePan(); _gdalEvents.Close(); return ATL::IOleObjectImpl::Close(saveOption); } @@ -845,5 +1085,6 @@ HRESULT CMapWinGISControl::OnDraw(ATL_DRAWINFO& drawInfo) CMapWinGISControl::~CMapWinGISControl() { + CancelMousePan(); _gdalEvents.Shutdown(); } diff --git a/src/MapWinGIS.ActiveX/MapWinGISControl.h b/src/MapWinGIS.ActiveX/MapWinGISControl.h index 4dad271..c3ff168 100644 --- a/src/MapWinGIS.ActiveX/MapWinGISControl.h +++ b/src/MapWinGIS.ActiveX/MapWinGISControl.h @@ -90,6 +90,12 @@ class ATL_NO_VTABLE CMapWinGISControl : END_PROP_MAP() BEGIN_MSG_MAP(CMapWinGISControl) + MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) + MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove) + MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp) + MESSAGE_HANDLER(WM_MOUSEWHEEL, OnMouseWheel) + MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged) + MESSAGE_HANDLER(WM_CANCELMODE, OnCancelMode) MESSAGE_HANDLER(WM_SIZE, OnSize) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) CHAIN_MSG_MAP(ATL::CComControl) @@ -134,6 +140,12 @@ class ATL_NO_VTABLE CMapWinGISControl : STDMETHOD(Close)(DWORD saveOption) override; STDMETHOD(SetObjectRects)(LPCRECT position, LPCRECT clip) override; + LRESULT OnLButtonDown(UINT, WPARAM, LPARAM, BOOL& handled); + LRESULT OnMouseMove(UINT, WPARAM, LPARAM, BOOL& handled); + LRESULT OnLButtonUp(UINT, WPARAM, LPARAM, BOOL& handled); + LRESULT OnMouseWheel(UINT, WPARAM, LPARAM, BOOL& handled); + LRESULT OnCaptureChanged(UINT, WPARAM, LPARAM, BOOL& handled); + LRESULT OnCancelMode(UINT, WPARAM, LPARAM, BOOL& handled); LRESULT OnSize(UINT, WPARAM, LPARAM, BOOL& handled); LRESULT OnGdalDispatch(UINT, WPARAM, LPARAM, BOOL& handled); LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled); @@ -147,11 +159,17 @@ class ATL_NO_VTABLE CMapWinGISControl : /// Resizes the view to a host size, clamped to the supported range, and /// returns true when the frame must be redrawn. bool ResizeView(LONG width, LONG height) noexcept; + bool CaptureMouse() noexcept; + void ReleaseMouseCapture() noexcept; + void CancelMousePan() noexcept; + bool GetMousePoint(LPARAM lParam, bool screenCoordinates, POINT& point) const noexcept; mwg::MapView _view; LONGLONG _featureCount = 0; mwg::activex::VectorLayerRenderer _renderer; mwg::activex::GdalEventSource _gdalEvents; + POINT _lastPanPoint{}; + bool _isPanning = false; }; OBJECT_ENTRY_AUTO(CLSID_MapWinGISControl, CMapWinGISControl) diff --git a/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp b/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp index 899cd17..fb3502f 100644 --- a/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp +++ b/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1835,6 +1836,139 @@ TEST(ActiveXHosting, ResizesRepaintsAndRecreatesControl) GetPixel(resizedSurface.Get(), resizedWidth / 2, resizedHeight / 2), RGB(61, 122, 169)); + ATL::CComQIPtr inputControl(hostedControl); + ASSERT_NE(inputControl.p, nullptr); + ATL::CComQIPtr navigationControl(hostedControl); + ASSERT_NE(navigationControl.p, nullptr); + BitmapSurface initialNavigationSurface(resizedWidth, resizedHeight); + BitmapSurface wheelNavigationSurface(resizedWidth, resizedHeight); + BitmapSurface panNavigationSurface(resizedWidth, resizedHeight); + ASSERT_TRUE(initialNavigationSurface.IsValid()); + ASSERT_TRUE(wheelNavigationSurface.IsValid()); + ASSERT_TRUE(panNavigationSurface.IsValid()); + ASSERT_TRUE(SUCCEEDED(viewObject->Draw( + DVASPECT_CONTENT, + -1, + nullptr, + nullptr, + nullptr, + initialNavigationSurface.Get(), + &rendererBounds, + nullptr, + nullptr, + 0))); + + const int anchorX = resizedWidth / 3; + const int anchorY = resizedHeight / 3; + POINT wheelPoint{ + resizedBounds.left + anchorX, + resizedBounds.top + anchorY}; + ASSERT_TRUE(ClientToScreen(hostWindow.Get(), &wheelPoint)); + LRESULT inputResult = 0; + ASSERT_EQ( + inputControl->OnWindowMessage( + WM_MOUSEWHEEL, + MAKEWPARAM(0, WHEEL_DELTA), + MAKELPARAM(wheelPoint.x, wheelPoint.y), + &inputResult), + S_OK); + ASSERT_TRUE(SUCCEEDED(viewObject->Draw( + DVASPECT_CONTENT, + -1, + nullptr, + nullptr, + nullptr, + wheelNavigationSurface.Get(), + &rendererBounds, + nullptr, + nullptr, + 0))); + EXPECT_TRUE(HasDifferentPixel( + initialNavigationSurface.Get(), + wheelNavigationSurface.Get(), + resizedWidth, + resizedHeight)); + + HWND controlWindow = nullptr; + const HRESULT getWindowResult = inputControl->GetWindow(&controlWindow); + POINT dragStart{anchorX, anchorY}; + if (getWindowResult == E_FAIL) + { + dragStart.x += resizedBounds.left; + dragStart.y += resizedBounds.top; + } + else + { + ASSERT_TRUE(SUCCEEDED(getWindowResult)); + ASSERT_NE(controlWindow, nullptr); + } + + constexpr int dragDeltaX = 24; + constexpr int dragDeltaY = 12; + ASSERT_EQ( + inputControl->OnWindowMessage( + WM_LBUTTONDOWN, + MK_LBUTTON, + MAKELPARAM(dragStart.x, dragStart.y), + &inputResult), + S_OK); + ASSERT_EQ( + inputControl->OnWindowMessage( + WM_MOUSEMOVE, + MK_LBUTTON, + MAKELPARAM( + dragStart.x + dragDeltaX, + dragStart.y + dragDeltaY), + &inputResult), + S_OK); + ASSERT_EQ( + inputControl->OnWindowMessage( + WM_LBUTTONUP, + 0, + MAKELPARAM( + dragStart.x + dragDeltaX, + dragStart.y + dragDeltaY), + &inputResult), + S_OK); + ASSERT_TRUE(SUCCEEDED(viewObject->Draw( + DVASPECT_CONTENT, + -1, + nullptr, + nullptr, + nullptr, + panNavigationSurface.Get(), + &rendererBounds, + nullptr, + nullptr, + 0))); + EXPECT_TRUE(HasDifferentPixel( + wheelNavigationSurface.Get(), + panNavigationSurface.Get(), + resizedWidth, + resizedHeight)); + + mwg::Viewport expectedNavigation(resizedWidth, resizedHeight); + ASSERT_TRUE(expectedNavigation.FitExtent({0, 0, 10, 5}, 12.0)); + ASSERT_TRUE(expectedNavigation.ZoomAt( + {static_cast(anchorX), static_cast(anchorY)}, + 1.2)); + const std::optional resolutionAfterWheel = + expectedNavigation.Resolution(); + ASSERT_TRUE(resolutionAfterWheel.has_value()); + ASSERT_TRUE(expectedNavigation.Pan( + -dragDeltaX * *resolutionAfterWheel, + dragDeltaY * *resolutionAfterWheel)); + const auto expectedFeaturePixel = + mwg::ToPixel(*expectedNavigation.MapToScreen({1, 2.5})); + ASSERT_TRUE(expectedFeaturePixel.has_value()); + EXPECT_EQ( + GetPixel( + panNavigationSurface.Get(), + expectedFeaturePixel->x, + expectedFeaturePixel->y), + RGB(61, 122, 169)); + ASSERT_EQ(navigationControl->FitToMap(), S_OK); + ATL::CComBSTR openGlPreference(L"OpenGL"); ASSERT_EQ( rendererControl->put_RenderingBackendPreference(openGlPreference), From b4997cd89478763b2979ba76831006548d3e0ae4 Mon Sep 17 00:00:00 2001 From: Paul Meems Date: Fri, 6 Nov 2026 15:55:27 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20fix(activex):=20address=20mo?= =?UTF-8?q?use=20navigation=20review=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Require successful windowless capture before starting a drag. - Compare hosted mouse navigation with explicit COM navigation. - Update the architecture guide for implemented navigation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/ARCHITECTURE.md | 10 +++---- src/MapWinGIS.ActiveX/MapWinGISControl.cpp | 2 +- .../ActiveXActivationTests.cpp | 28 +++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 29142b1..e9891de 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -398,9 +398,9 @@ Windows CPU fallback; Core has no independent platform-neutral renderer. The C# host's pixel assertions, activation, and native tests pass for Debug and Release on Win32 and x64 in an interactive desktop session. Core navigation on the shared `MapView` (zoom to map and layer, zoom in and -out, anchored zoom, and pan) is implemented but not yet exposed through COM or -mouse input; that is the next milestone. Deep zoom can push a layer past the -GDI coordinate range until clipping exists. Measure scene preparation with -representative datasets before adding caching, paging, or broader OpenGL -geometry support. +out, anchored zoom, and pan) is exposed through `IMapWinGISControl4`; the +ActiveX control also supports left-button drag panning and cursor-anchored +wheel zoom. Deep zoom can push a layer past the GDI coordinate range until +clipping exists. Measure scene preparation with representative datasets +before adding caching, paging, or broader OpenGL geometry support. OpenCL and full MapWinGIS 5 API parity remain deferred. diff --git a/src/MapWinGIS.ActiveX/MapWinGISControl.cpp b/src/MapWinGIS.ActiveX/MapWinGISControl.cpp index 9ae5ba5..79507dd 100644 --- a/src/MapWinGIS.ActiveX/MapWinGISControl.cpp +++ b/src/MapWinGIS.ActiveX/MapWinGISControl.cpp @@ -716,7 +716,7 @@ bool CMapWinGISControl::CaptureMouse() noexcept if (m_bWndLess) { return m_spInPlaceSite != nullptr && - SUCCEEDED(m_spInPlaceSite->SetCapture(TRUE)); + m_spInPlaceSite->SetCapture(TRUE) == S_OK; } if (m_hWnd == nullptr) diff --git a/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp b/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp index fb3502f..0da2760 100644 --- a/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp +++ b/tests/MapWinGIS.ActiveX.Tests/ActiveXActivationTests.cpp @@ -1843,9 +1843,12 @@ TEST(ActiveXHosting, ResizesRepaintsAndRecreatesControl) BitmapSurface initialNavigationSurface(resizedWidth, resizedHeight); BitmapSurface wheelNavigationSurface(resizedWidth, resizedHeight); BitmapSurface panNavigationSurface(resizedWidth, resizedHeight); + BitmapSurface expectedNavigationSurface(resizedWidth, resizedHeight); ASSERT_TRUE(initialNavigationSurface.IsValid()); ASSERT_TRUE(wheelNavigationSurface.IsValid()); ASSERT_TRUE(panNavigationSurface.IsValid()); + ASSERT_TRUE(expectedNavigationSurface.IsValid()); + ASSERT_EQ(navigationControl->FitToMap(), S_OK); ASSERT_TRUE(SUCCEEDED(viewObject->Draw( DVASPECT_CONTENT, -1, @@ -1958,6 +1961,31 @@ TEST(ActiveXHosting, ResizesRepaintsAndRecreatesControl) ASSERT_TRUE(expectedNavigation.Pan( -dragDeltaX * *resolutionAfterWheel, dragDeltaY * *resolutionAfterWheel)); + ASSERT_EQ(navigationControl->FitToMap(), S_OK); + ASSERT_EQ( + navigationControl->ZoomAt(anchorX, anchorY, 1.2), + S_OK); + ASSERT_EQ( + navigationControl->Pan( + -dragDeltaX * *resolutionAfterWheel, + dragDeltaY * *resolutionAfterWheel), + S_OK); + ASSERT_TRUE(SUCCEEDED(viewObject->Draw( + DVASPECT_CONTENT, + -1, + nullptr, + nullptr, + nullptr, + expectedNavigationSurface.Get(), + &rendererBounds, + nullptr, + nullptr, + 0))); + EXPECT_FALSE(HasDifferentPixel( + panNavigationSurface.Get(), + expectedNavigationSurface.Get(), + resizedWidth, + resizedHeight)); const auto expectedFeaturePixel = mwg::ToPixel(*expectedNavigation.MapToScreen({1, 2.5})); ASSERT_TRUE(expectedFeaturePixel.has_value());