Repository: organicmaps/organicmaps (2026-03-20T10:46:24Z)

Issue:
[ios] Editor crosshair doesn't point to the same selected place when "Add Place" is pressed
1. Select any empty place on the map.
2. Copy its coordinates.
3. Open search and insert coordinates.
4. Tap at the found result with coordinates.
5. Expand Place Page and press Add Place

Observe that crosshair is not centered at the same coordinates, but shifted instead.

In other scenarios crosshair points correctly at the recently selected place.


Discussion:
Hi @biodranik,

I've confirmed the crosshair shifts away from the searched coordinates (video attached). I’d like to investigate the logic causing this offset and submit a fix. Does this implementation focus align with the project's goals?

Thanks!


https://github.com/user-attachments/assets/c6e371a8-1b2e-4c8c-aca1-559e55535fc4
You don't need to ask permission to submit a fix, just do it ;)
@jmegaraujo There was an attempt to fix the crosshair jumping issue in the [#11262](https://github.com/organicmaps/organicmaps/pull/11262). But is seems like the fix does not cover all cases.  You can check how the crosshair works in my PR.



Patch:
diff --git a/libs/drape_frontend/frontend_renderer.cpp b/libs/drape_frontend/frontend_renderer.cpp
index 48c14fb09c9..e0a0d0762f0 100644
--- a/libs/drape_frontend/frontend_renderer.cpp
+++ b/libs/drape_frontend/frontend_renderer.cpp
@@ -772,7 +772,7 @@ void FrontendRenderer::AcceptMessage(ref_ptr<Message> message)
           zoom = scales::GetAddNewPlaceScale();
         AddUserEvent(make_unique_dp<SetCenterEvent>(
             pt ? *pt : m_userEventStream.GetCurrentScreen().GlobalRect().Center(), zoom, true /* isAnim */,
-            true /* trackVisibleViewport */, nullptr /* parallelAnimCreator */));
+            !pt /* trackVisibleViewport */, nullptr /* parallelAnimCreator */));
       }
       else
       {
diff --git a/libs/drape_frontend/user_event_stream.cpp b/libs/drape_frontend/user_event_stream.cpp
index 0ee07be04b6..dac7b5c53bd 100644
--- a/libs/drape_frontend/user_event_stream.cpp
+++ b/libs/drape_frontend/user_event_stream.cpp
@@ -414,7 +414,12 @@ bool UserEventStream::OnSetCenter(ref_ptr<SetCenterEvent> centerEvent)
   auto const zoom = centerEvent->GetZoom();
   auto const scaleFactor = centerEvent->GetScaleFactor();
 
-  if (centerEvent->TrackVisibleViewport())
+  bool const trackViewport = centerEvent->TrackVisibleViewport();
+
+  m2::PointD const pixelTarget =
+      trackViewport ? m_visibleViewport.Center() : GetCurrentScreen().PixelRectIn3d().Center();
+
+  if (trackViewport)
   {
     m_needTrackCenter = true;
     m_trackedCenter = center;
@@ -425,17 +430,17 @@ bool UserEventStream::OnSetCenter(ref_ptr<SetCenterEvent> centerEvent)
   if (zoom != kDoNotChangeZoom)
   {
     screen.SetFromParams(center, screen.GetAngle(), GetScreenScale(zoom));
-    screen.MatchGandP3d(center, m_visibleViewport.Center());
+    screen.MatchGandP3d(center, pixelTarget);
   }
   else if (scaleFactor > 0.0)
   {
     screen.SetOrg(center);
-    ApplyScale(m_visibleViewport.Center(), scaleFactor, screen);
+    ApplyScale(pixelTarget, scaleFactor, screen);
   }
   else
   {
     GetTargetScreen(screen);
-    screen.MatchGandP3d(center, m_visibleViewport.Center());
+    screen.MatchGandP3d(center, pixelTarget);
   }
 
   ShrinkAndScaleInto(screen, df::GetWorldRect());


Test patch:
diff --git a/libs/drape_frontend/drape_frontend_tests/user_event_stream_tests.cpp b/libs/drape_frontend/drape_frontend_tests/user_event_stream_tests.cpp
index bd60ef8fc45..59bfc3f0005 100644
--- a/libs/drape_frontend/drape_frontend_tests/user_event_stream_tests.cpp
+++ b/libs/drape_frontend/drape_frontend_tests/user_event_stream_tests.cpp
@@ -4,6 +4,7 @@
 
 #include "base/thread.hpp"
 
+#include <cmath>
 #include <cstring>
 #include <functional>
 #include <list>
@@ -54,6 +55,21 @@ class UserEventStreamTest : df::UserEventStream::Listener
                                                        nullptr /* parallelAnimCreator */));
   }
 
+  void AddResizeEvent(uint32_t w, uint32_t h) { m_stream.AddEvent(make_unique_dp<df::ResizeEvent>(w, h)); }
+
+  void AddSetVisibleViewport(m2::RectD const & rect)
+  {
+    m_stream.AddEvent(make_unique_dp<df::SetVisibleViewportEvent>(rect));
+  }
+
+  void AddSetCenter(m2::PointD const & center, int zoom, bool trackVisibleViewport)
+  {
+    m_stream.AddEvent(make_unique_dp<df::SetCenterEvent>(center, zoom, false /* isAnim */, trackVisibleViewport,
+                                                         nullptr /* parallelAnimCreator */));
+  }
+
+  ScreenBase const & GetScreen() const { return m_stream.GetCurrentScreen(); }
+
   void AddExpectation(char const * action) { m_expectation.push_back(action); }
 
   void RunTest()
@@ -195,4 +211,87 @@ UNIT_TEST(SimpleScale)
   test.RunTest();
 }
 
+UNIT_TEST(SetCenter_TrackVisibleViewport_False_UsesGeometricScreenCenter)
+{
+  // Simulates "Add Place" triggered from a search result with an explicit coordinate.
+  UserEventStreamTest test(false);
+
+  uint32_t const kW = 1000, kH = 1000;
+  m2::RectD const kVisibleViewport(0.0, 0.0, static_cast<double>(kW), 700.0);
+
+  test.AddResizeEvent(kW, kH);
+  test.AddSetVisibleViewport(kVisibleViewport);
+  test.SetRect(m2::RectD(-100.0, -100.0, 100.0, 100.0));
+  test.RunTest();
+
+  m2::PointD const kTarget(0.0, 0.0);
+  test.AddSetCenter(kTarget, df::kDoNotChangeZoom, false /* trackVisibleViewport */);
+  test.RunTest();
+
+  ScreenBase const & screen = test.GetScreen();
+  m2::PointD const pixelPos = screen.GtoP(kTarget);
+
+  double const kEps = 1.0;  // 1 pixel tolerance
+  double const expectedX = kW * 0.5;
+  double const expectedY = kH * 0.5;
+  TEST(std::fabs(pixelPos.x - expectedX) < kEps, (pixelPos.x, "expected", expectedX));
+  TEST(std::fabs(pixelPos.y - expectedY) < kEps, (pixelPos.y, "expected", expectedY));
+}
+
+UNIT_TEST(SetCenter_TrackVisibleViewport_True_UsesVisibleViewportCenter)
+{
+  // Normal navigation (no explicit position): trackVisibleViewport=true
+  UserEventStreamTest test(false);
+
+  uint32_t const kW = 1000, kH = 1000;
+  m2::RectD const kVisibleViewport(0.0, 0.0, static_cast<double>(kW), 700.0);
+
+  test.AddResizeEvent(kW, kH);
+  test.AddSetVisibleViewport(kVisibleViewport);
+  test.SetRect(m2::RectD(-100.0, -100.0, 100.0, 100.0));
+  test.RunTest();
+
+  m2::PointD const kTarget(0.0, 0.0);
+  test.AddSetCenter(kTarget, df::kDoNotChangeZoom, true /* trackVisibleViewport */);
+  test.RunTest();
+
+  ScreenBase const & screen = test.GetScreen();
+  m2::PointD const pixelPos = screen.GtoP(kTarget);
+
+  double const kEps = 1.0;
+  m2::PointD const viewportCenter = kVisibleViewport.Center();
+  TEST(std::fabs(pixelPos.x - viewportCenter.x) < kEps, (pixelPos.x, "expected", viewportCenter.x));
+  TEST(std::fabs(pixelPos.y - viewportCenter.y) < kEps, (pixelPos.y, "expected", viewportCenter.y));
+}
+
+UNIT_TEST(SetCenter_TrackVisibleViewport_DifferentResults_WithAsymmetricViewport)
+{
+  // Regression guard: with an asymmetric visible viewport (UI panel present),
+  // trackVisibleViewport=false and trackVisibleViewport=true
+  uint32_t const kW = 1000, kH = 1000;
+  m2::RectD const kVisibleViewport(0.0, 0.0, static_cast<double>(kW), 700.0);
+  m2::PointD const kTarget(0.0, 0.0);
+
+  UserEventStreamTest testFalse(false);
+  testFalse.AddResizeEvent(kW, kH);
+  testFalse.AddSetVisibleViewport(kVisibleViewport);
+  testFalse.SetRect(m2::RectD(-100.0, -100.0, 100.0, 100.0));
+  testFalse.RunTest();
+  testFalse.AddSetCenter(kTarget, df::kDoNotChangeZoom, false);
+  testFalse.RunTest();
+  m2::PointD const pixelFalse = testFalse.GetScreen().GtoP(kTarget);
+
+  UserEventStreamTest testTrue(false);
+  testTrue.AddResizeEvent(kW, kH);
+  testTrue.AddSetVisibleViewport(kVisibleViewport);
+  testTrue.SetRect(m2::RectD(-100.0, -100.0, 100.0, 100.0));
+  testTrue.RunTest();
+  testTrue.AddSetCenter(kTarget, df::kDoNotChangeZoom, true);
+  testTrue.RunTest();
+  m2::PointD const pixelTrue = testTrue.GetScreen().GtoP(kTarget);
+
+  double const yDiff = std::fabs(pixelFalse.y - pixelTrue.y);
+  TEST(yDiff > 100.0, (yDiff, "Expected ~150px difference in Y due to 300px bottom panel"));
+}
+
 #endif