From 4887172a59c323a220987f744daed68f251e27a3 Mon Sep 17 00:00:00 2001 From: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:59:26 +0100 Subject: [PATCH] Fix `ViewPanner` panning mouse warp Currently the mouse cursor jumps in unexpected ways, when a `ViewPanner` is used in SubViewports or embedded Windows. This is caused by providing wrong coordinate systems to Input::warp_mouse_motion. This PR replaces the use of `Input::warp_mouse_motion` with `Viewport::wrap_mouse_in_rect` and makes sure, that the correct coordinate systems are used. This change makes it necessary, that all classes, that currently use ViewPanner, need to provide the correct Viewport to ViewPanner. --- editor/animation_bezier_editor.cpp | 1 + editor/animation_track_editor.cpp | 1 + .../animation_blend_tree_editor_plugin.cpp | 1 + editor/plugins/canvas_item_editor_plugin.cpp | 1 + editor/plugins/polygon_2d_editor_plugin.cpp | 1 + editor/plugins/sprite_2d_editor_plugin.cpp | 4 ++- .../plugins/texture_region_editor_plugin.cpp | 7 ++-- editor/plugins/tiles/tile_atlas_view.cpp | 1 + .../plugins/visual_shader_editor_plugin.cpp | 1 + scene/debugger/scene_debugger.cpp | 1 + scene/gui/graph_edit.cpp | 4 +++ scene/gui/view_panner.cpp | 9 +++-- scene/gui/view_panner.h | 4 +++ scene/main/viewport.cpp | 34 +++++++++++++++++++ scene/main/viewport.h | 1 + 15 files changed, 64 insertions(+), 7 deletions(-) diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 36ca4176387..e352b57ba32 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -222,6 +222,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + panner->set_viewport(get_viewport()); [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index ae2b63deadd..5f7433ac2a3 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -5141,6 +5141,7 @@ void AnimationTrackEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + panner->set_viewport(get_viewport()); [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 175aab05fbd..b4581afdc46 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -942,6 +942,7 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { _update_editor_settings(); + graph->get_panner()->set_viewport(get_viewport()); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index c242357a9ef..edf27146927 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4178,6 +4178,7 @@ void CanvasItemEditor::_notification(int p_what) { AnimationPlayerEditor::get_singleton()->connect("animation_selected", callable_mp(this, &CanvasItemEditor::_keying_changed).unbind(1)); _keying_changed(); _update_editor_settings(); + panner->set_viewport(get_viewport()); connect("item_lock_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button)); connect("item_group_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button)); diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 6fc4bef71eb..60cbfd59952 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -108,6 +108,7 @@ void Polygon2DEditor::_notification(int p_what) { } case NOTIFICATION_ENTER_TREE: { uv_panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + uv_panner->set_viewport(get_viewport()); } break; case NOTIFICATION_READY: { diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index c365ad61338..f518eb6ecfa 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -558,7 +558,9 @@ void Sprite2DEditor::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); } break; - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { + panner->set_viewport(get_viewport()); + } break; case NOTIFICATION_THEME_CHANGED: { options->set_button_icon(get_editor_theme_icon(SNAME("Sprite2D"))); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index bd653e4eeda..21f7c812f2f 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -824,10 +824,6 @@ void TextureRegionEditor::_notification(int p_what) { [[fallthrough]]; } - case NOTIFICATION_READY: { - panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); - } break; - case NOTIFICATION_ENTER_TREE: { get_tree()->connect("node_removed", callable_mp(this, &TextureRegionEditor::_node_removed)); @@ -835,6 +831,9 @@ void TextureRegionEditor::_notification(int p_what) { if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) { _update_autoslice(); } + + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + panner->set_viewport(get_viewport()); } break; case NOTIFICATION_EXIT_TREE: { diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index bafacf8e36d..144493ab40b 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -615,6 +615,7 @@ void TileAtlasView::_notification(int p_what) { } case NOTIFICATION_ENTER_TREE: { panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + panner->set_viewport(get_viewport()); } break; case NOTIFICATION_THEME_CHANGED: { diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 1162943b4a8..bf1d441c6f9 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -5188,6 +5188,7 @@ void VisualShaderEditor::_notification(int p_what) { } graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + graph->get_panner()->set_viewport(get_viewport()); graph->set_warped_panning(bool(EDITOR_GET("editors/panning/warped_mouse_panning"))); } break; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index f369aec569e..1e9e961cf61 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -1250,6 +1250,7 @@ void RuntimeNodeSelect::_setup(const Dictionary &p_settings) { int pan_speed = p_settings.get("editors/panning/2d_editor_pan_speed", 20); Array keys = p_settings.get("canvas_item_editor/pan_view", Array()).operator Array(); panner->setup(panning_scheme, DebuggerMarshalls::deserialize_key_shortcut(keys), simple_panning); + panner->set_viewport(root); panner->set_scroll_speed(pan_speed); warped_panning = p_settings.get("editors/panning/warped_mouse_panning", false); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index bcf0f5bdc89..e909b4bc48e 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -785,6 +785,10 @@ void GraphEdit::_notification(int p_what) { minimap->queue_redraw(); callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred(); } break; + + case NOTIFICATION_ENTER_TREE: { + panner->set_viewport(get_viewport()); + } break; } } diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp index 438a228c4ff..a436e662fd0 100644 --- a/scene/gui/view_panner.cpp +++ b/scene/gui/view_panner.cpp @@ -33,6 +33,7 @@ #include "core/input/input.h" #include "core/input/shortcut.h" #include "core/os/keyboard.h" +#include "scene/main/viewport.h" bool ViewPanner::gui_input(const Ref &p_event, Rect2 p_canvas_rect) { Ref mb = p_event; @@ -109,8 +110,8 @@ bool ViewPanner::gui_input(const Ref &p_event, Rect2 p_canvas_rect) Ref mm = p_event; if (mm.is_valid()) { if (is_dragging) { - if (p_canvas_rect != Rect2()) { - pan_callback.call(Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect), p_event); + if (viewport && p_canvas_rect != Rect2()) { + pan_callback.call(viewport->wrap_mouse_in_rect(mm->get_relative(), p_canvas_rect), p_event); } else { pan_callback.call(mm->get_relative(), p_event); } @@ -212,6 +213,10 @@ void ViewPanner::setup(ControlScheme p_scheme, Ref p_shortcut, bool p_ set_simple_panning_enabled(p_simple_panning); } +void ViewPanner::set_viewport(Viewport *p_viewport) { + viewport = p_viewport; +} + bool ViewPanner::is_panning() const { return is_dragging || pan_key_pressed; } diff --git a/scene/gui/view_panner.h b/scene/gui/view_panner.h index 5aec2d4f6b5..7cdd43e1240 100644 --- a/scene/gui/view_panner.h +++ b/scene/gui/view_panner.h @@ -35,6 +35,7 @@ class InputEvent; class Shortcut; +class Viewport; class ViewPanner : public RefCounted { GDCLASS(ViewPanner, RefCounted); @@ -63,6 +64,8 @@ private: bool enable_rmb = false; bool simple_panning_enabled = false; + Viewport *viewport = nullptr; + Ref pan_view_shortcut; Callable pan_callback; @@ -81,6 +84,7 @@ public: void set_pan_axis(PanAxis p_pan_axis); void setup(ControlScheme p_scheme, Ref p_shortcut, bool p_simple_panning); + void set_viewport(Viewport *p_viewport); bool is_panning() const; void set_force_drag(bool p_force); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 4b89167ce99..b90352add5a 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1377,6 +1377,40 @@ void Viewport::warp_mouse(const Vector2 &p_position) { Input::get_singleton()->warp_mouse(gpos); } +Point2 Viewport::wrap_mouse_in_rect(const Vector2 &p_relative, const Rect2 &p_rect) { + // Move the mouse cursor from its current position to a location bounded by `p_rect` + // in accordance with a heuristic that takes the traveled distance `p_relative` of the mouse + // into account. + + // All parameters are in viewport coordinates. + // p_relative denotes the distance to the previous mouse position. + // p_rect denotes the area, in which the mouse should be confined in. + + // The relative distance reported for the next event after a warp is in the boundaries of the + // size of the rect on that axis, but it may be greater, in which case there's no problem as + // fmod() will warp it, but if the pointer has moved in the opposite direction between the + // pointer relocation and the subsequent event, the reported relative distance will be less + // than the size of the rect and thus fmod() will be disabled for handling the situation. + // And due to this mouse warping mechanism being stateless, we need to apply some heuristics + // to detect the warp: if the relative distance is greater than the half of the size of the + // relevant rect (checked per each axis), it will be considered as the consequence of a former + // pointer warp. + + const Point2 rel_sign(p_relative.x >= 0.0f ? 1 : -1, p_relative.y >= 0.0 ? 1 : -1); + const Size2 warp_margin = p_rect.size * 0.5f; + const Point2 rel_warped( + Math::fmod(p_relative.x + rel_sign.x * warp_margin.x, p_rect.size.x) - rel_sign.x * warp_margin.x, + Math::fmod(p_relative.y + rel_sign.y * warp_margin.y, p_rect.size.y) - rel_sign.y * warp_margin.y); + + const Point2 pos_local = get_mouse_position() - p_rect.position; + const Point2 pos_warped(Math::fposmod(pos_local.x, p_rect.size.x), Math::fposmod(pos_local.y, p_rect.size.y)); + if (pos_warped != pos_local) { + warp_mouse(pos_warped + p_rect.position); + } + + return rel_warped; +} + void Viewport::_gui_sort_roots() { if (!gui.roots_order_dirty) { return; diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 4dee21646b8..38514f23875 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -586,6 +586,7 @@ public: Vector2 get_mouse_position() const; void warp_mouse(const Vector2 &p_position); + Point2 wrap_mouse_in_rect(const Vector2 &p_relative, const Rect2 &p_rect); virtual void update_mouse_cursor_state(); void set_physics_object_picking(bool p_enable);