From bedef1f2af6b1e70818e9ad34019311779e4b7bb Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 19 Oct 2026 18:54:08 +0200 Subject: [PATCH] fix(editor): a touchpad no longer flips through the shapes nor throws the zoom far out A touchpad under Wayland sends a scroll as many fractions of a notch. Shift+wheel took each of them for a step and went round the shapes of the laid track, the joins and the connections several times in one swipe; now the fractions are gathered into whole notches and a gesture moves such a choice one step. Ctrl+wheel steps the radius once a notch. The 3D zoom measured to the level 0 when nothing was under the cursor, which in a scenery on the terrain model lies far below or kilometres ahead, so a light swipe at the horizon flew the camera away. It measures to the ground under the camera now, backs away by no more than four heights of the camera over it, and takes at most two notches at a time. Self-test laystate tells the shape, the radius and the camera. --- application/editormode.cpp | 20 +++++++++++------ application/editormode.h | 11 ++++++++++ application/editormodelay.cpp | 30 ++++++++++++++++++++++++-- application/editormodeselftestjoin.cpp | 6 ++++++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index a6d3772c..a1cac045 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -5886,6 +5886,8 @@ void editor_mode::on_scroll(double const Xoffset, double const Yoffset) return; if (ImGui::GetIO().WantCaptureMouse || Yoffset == 0.0) return; + // a fast swipe on a touchpad can come as one big step + auto const notches{std::clamp(Yoffset, -2.0, 2.0)}; if (false == Global.EditorOrtho) { // in 3D the camera flies along the ray under the cursor, by a share of the distance to what it points at; @@ -5894,20 +5896,26 @@ void editor_mode::on_scroll(double const Xoffset, double const Yoffset) glm::dvec3 origin; glm::dvec3 direction; projection.ray(ImGui::GetIO().MousePos, origin, direction); + // with nothing under the cursor, the ground under the camera stands for it: the level 0 can lie far below the terrain + std::vector found; + auto const below{ground_heights({Camera.Pos}, false, &found)}; + auto const ground{found[0] != 0 ? below[0] : 0.0}; + auto const height{std::max(2.0, std::abs(Camera.Pos.y - ground))}; glm::dvec3 level; - auto const distance{std::min( - GfxRenderer->Mouse_Hit() ? glm::length(GfxRenderer->Mouse_Position()) : projection.on_level(ImGui::GetIO().MousePos, 0.0, level) ? glm::distance(level, origin) : std::max(20.0, std::abs(Camera.Pos.y) * 2.0), - 20000.0)}; - auto step{distance * (1.0 - std::pow(0.85, Yoffset))}; + auto distance{std::min(GfxRenderer->Mouse_Hit() ? glm::length(GfxRenderer->Mouse_Position()) : projection.on_level(ImGui::GetIO().MousePos, ground, level) ? glm::distance(level, origin) : 2.0 * height, 20000.0)}; + // backing away from a point on the horizon would throw the camera kilometres up + if (notches < 0.0) + distance = std::min(distance, std::max(100.0, 4.0 * height)); + auto step{distance * (1.0 - std::pow(0.85, notches))}; // no nearer than 2 m to the point, not through it - if (Yoffset > 0.0) + if (notches > 0.0) step = std::max(0.0, std::min(step, distance - 2.0)); Camera.Pos += direction * step; Global.pCamera = Camera; return; } auto const before{Global.EditorOrthoExtent}; - auto const after{std::clamp(before * static_cast(std::pow(0.85, Yoffset)), 5.0f, 50000.0f)}; + auto const after{std::clamp(before * static_cast(std::pow(0.85, notches)), 5.0f, 50000.0f)}; screen_projection const projection; glm::dvec3 origin; glm::dvec3 direction; diff --git a/application/editormode.h b/application/editormode.h index 42040efa..f154adad 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1476,6 +1476,17 @@ class editor_mode : public application_mode, private editor_track::observer editor_track::point_ref m_join_point; mutable alignment::join_result m_join_last; bool track_wheel(double const Offset); + // a touchpad sends fractions of a notch: a choice moves a step for each whole notch gathered, and only one in a gesture when + // Gesture; a pause, another key or the other way starts over + struct wheel_state + { + double rest{0.0}; + double time{-10.0}; + int keys{0}; + bool spent{false}; + }; + wheel_state m_wheel; + int wheel_step(double const Offset, bool const Gesture); std::string track_shaping(bool const Joined) const; void render_layout_combo(); double design_speed(TTrack const *From, TTrack const *To) const; diff --git a/application/editormodelay.cpp b/application/editormodelay.cpp index 40155f80..d66b7608 100644 --- a/application/editormodelay.cpp +++ b/application/editormodelay.cpp @@ -696,13 +696,37 @@ void editor_mode::render_layout_combo() ImGui::TextDisabled("%s", STR_C("(Shift+wheel)")); } +int editor_mode::wheel_step(double const Offset, bool const Gesture) +{ + auto const &io{ImGui::GetIO()}; + auto const now{ImGui::GetTime()}; + int const keys{(io.KeyCtrl ? 1 : 0) | (io.KeyShift ? 2 : 0) | (io.KeyAlt ? 4 : 0)}; + auto &wheel{m_wheel}; + if (now - wheel.time > 0.3 || keys != wheel.keys || (wheel.rest != 0.0 && (wheel.rest > 0.0) != (Offset > 0.0))) + { + wheel.rest = 0.0; + wheel.spent = false; + } + wheel.time = now; + wheel.keys = keys; + wheel.rest += Offset; + if ((Gesture && wheel.spent) || std::abs(wheel.rest) < 1.0 - 1e-6) + return 0; + int const step{wheel.rest > 0.0 ? 1 : -1}; + wheel.rest -= step; + wheel.spent = true; + return step; +} + bool editor_mode::track_wheel(double const Offset) { auto const &io{ImGui::GetIO()}; if (m_switch.placing && m_switch.connection >= 0 && Offset != 0.0 && (io.KeyCtrl || io.KeyShift || io.KeyAlt)) { auto &tool{m_switch}; - int const step{Offset > 0.0 ? 1 : -1}; + int const step{wheel_step(Offset, false == io.KeyCtrl)}; + if (step == 0) + return true; if (io.KeyShift) tool.connection = (tool.connection + step + connection::kind_count) % connection::kind_count; else if (io.KeyCtrl) @@ -723,7 +747,9 @@ bool editor_mode::track_wheel(double const Offset) bool const laying{m_lay.active && false == m_lay.points.empty()}; if (false == m_extend.active && false == laying) return false; - int const step{Offset > 0.0 ? 1 : -1}; + int const step{wheel_step(Offset, false == io.KeyCtrl)}; + if (step == 0) + return true; editor_track::snap_target target; bool const joined{m_extend.active ? extend_snap(target) : m_lay.mouse_snap.track != nullptr && m_lay.start.track != nullptr}; static double const radii[]{100.0, 150.0, 190.0, 250.0, 300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1200.0, 1500.0, 2000.0, 3000.0, 4000.0, 6000.0}; diff --git a/application/editormodeselftestjoin.cpp b/application/editormodeselftestjoin.cpp index 0ff46612..17f1791d 100644 --- a/application/editormodeselftestjoin.cpp +++ b/application/editormodeselftestjoin.cpp @@ -169,6 +169,12 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma lay_cancel(); return selftest_result::handled; } + if (command == "laystate") + { + WriteLog(format("SELFTEST laystate: layout %d, join variant %d, R %.0f m, join R %.0f m, camera %.1f %.1f %.1f, extent %.1f", static_cast(m_track_layout), m_join_variant, m_lay.radius, m_join_radius, Camera.Pos.x, + Camera.Pos.y, Camera.Pos.z, static_cast(Global.EditorOrthoExtent))); + return selftest_result::handled; + } if (command == "layclicks") { int layout{0};