ソースを参照

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.
Markus Sauermann 7 ヶ月 前
コミット
4887172a59

+ 1 - 0
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: {

+ 1 - 0
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: {

+ 1 - 0
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: {

+ 1 - 0
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));

+ 1 - 0
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: {

+ 3 - 1
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")));
 

+ 3 - 4
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: {

+ 1 - 0
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: {

+ 1 - 0
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;
 

+ 1 - 0
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);
 

+ 4 - 0
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;
 	}
 }
 

+ 7 - 2
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<InputEvent> &p_event, Rect2 p_canvas_rect) {
 	Ref<InputEventMouseButton> mb = p_event;
@@ -109,8 +110,8 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
 	Ref<InputEventMouseMotion> 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<Shortcut> 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;
 }

+ 4 - 0
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<Shortcut> 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<Shortcut> p_shortcut, bool p_simple_panning);
+	void set_viewport(Viewport *p_viewport);
 
 	bool is_panning() const;
 	void set_force_drag(bool p_force);

+ 34 - 0
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;

+ 1 - 0
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);