Browse Source

Add ViewPanner to 2D editor

kobewi 3 years ago
parent
commit
74bfe88267

+ 14 - 28
editor/animation_bezier_editor.cpp

@@ -218,7 +218,7 @@ void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const V
 
 
 void AnimationBezierTrackEdit::_notification(int p_what) {
 void AnimationBezierTrackEdit::_notification(int p_what) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
-		panner->set_control_scheme((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/animation_editors_panning_scheme").operator int());
+		panner->setup((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
 	}
 	}
 	if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
 	if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
 		close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons")));
 		close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons")));
@@ -632,27 +632,6 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 	}
 	}
 
 
 	Ref<InputEventMouseButton> mb = p_event;
 	Ref<InputEventMouseButton> mb = p_event;
-	if (mb.is_valid() && mb->is_pressed() && mb->is_alt_pressed()) {
-		// Alternate zoom (doesn't affect timeline).
-		if (mb->get_button_index() == MouseButton::WHEEL_DOWN) {
-			const float v_zoom_orig = v_zoom;
-			if (v_zoom < 100000) {
-				v_zoom *= 1.2;
-			}
-			v_scroll = v_scroll + (mb->get_position().y - get_size().y / 2) * (v_zoom - v_zoom_orig);
-			update();
-		}
-
-		if (mb->get_button_index() == MouseButton::WHEEL_UP) {
-			const float v_zoom_orig = v_zoom;
-			if (v_zoom > 0.000001) {
-				v_zoom /= 1.2;
-			}
-			v_scroll = v_scroll + (mb->get_position().y - get_size().y / 2) * (v_zoom - v_zoom_orig);
-			update();
-		}
-	}
-
 	if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
 	if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
 		menu_insert_key = mb->get_position();
 		menu_insert_key = mb->get_position();
 		if (menu_insert_key.x >= timeline->get_name_limit() && menu_insert_key.x <= get_size().width - timeline->get_buttons_width()) {
 		if (menu_insert_key.x >= timeline->get_name_limit() && menu_insert_key.x <= get_size().width - timeline->get_buttons_width()) {
@@ -1026,12 +1005,21 @@ void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec) {
 	update();
 	update();
 }
 }
 
 
-void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin) {
+void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
 	const float v_zoom_orig = v_zoom;
 	const float v_zoom_orig = v_zoom;
-	if (p_scroll_vec.y > 0) {
-		timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05);
+	if (p_alt) {
+		// Alternate zoom (doesn't affect timeline).
+		if (p_scroll_vec.y > 0) {
+			v_zoom = MIN(v_zoom * 1.2, 100000);
+		} else {
+			v_zoom = MAX(v_zoom / 1.2, 0.000001);
+		}
 	} else {
 	} else {
-		timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
+		if (p_scroll_vec.y > 0) {
+			timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05);
+		} else {
+			timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
+		}
 	}
 	}
 	v_scroll = v_scroll + (p_origin.y - get_size().y / 2) * (v_zoom - v_zoom_orig);
 	v_scroll = v_scroll + (p_origin.y - get_size().y / 2) * (v_zoom - v_zoom_orig);
 	update();
 	update();
@@ -1172,8 +1160,6 @@ void AnimationBezierTrackEdit::_bind_methods() {
 AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
 AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
 	panner.instantiate();
 	panner.instantiate();
 	panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_scroll_callback), callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback));
 	panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_scroll_callback), callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback));
-	panner->set_disable_rmb(true);
-	panner->set_control_scheme(ViewPanner::SCROLL_PANS);
 
 
 	play_position = memnew(Control);
 	play_position = memnew(Control);
 	play_position->set_mouse_filter(MOUSE_FILTER_PASS);
 	play_position->set_mouse_filter(MOUSE_FILTER_PASS);

+ 1 - 1
editor/animation_bezier_editor.h

@@ -128,7 +128,7 @@ class AnimationBezierTrackEdit : public Control {
 	Ref<ViewPanner> panner;
 	Ref<ViewPanner> panner;
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
-	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right);
 	void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right);
 	void _draw_track(int p_track, const Color &p_color);
 	void _draw_track(int p_track, const Color &p_color);

+ 6 - 8
editor/animation_track_editor.cpp

@@ -1459,7 +1459,7 @@ int AnimationTimelineEdit::get_name_limit() const {
 
 
 void AnimationTimelineEdit::_notification(int p_what) {
 void AnimationTimelineEdit::_notification(int p_what) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
-		panner->set_control_scheme((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/animation_editors_panning_scheme").operator int());
+		panner->setup((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
 	}
 	}
 
 
 	if (p_what == NOTIFICATION_ENTER_TREE) {
 	if (p_what == NOTIFICATION_ENTER_TREE) {
@@ -1843,7 +1843,7 @@ void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec) {
 	set_value(get_value() - p_scroll_vec.x / get_zoom_scale());
 	set_value(get_value() - p_scroll_vec.x / get_zoom_scale());
 }
 }
 
 
-void AnimationTimelineEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin) {
+void AnimationTimelineEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
 	if (p_scroll_vec.y < 0) {
 	if (p_scroll_vec.y < 0) {
 		get_zoom()->set_value(get_zoom()->get_value() * 1.05);
 		get_zoom()->set_value(get_zoom()->get_value() * 1.05);
 	} else {
 	} else {
@@ -1932,8 +1932,6 @@ AnimationTimelineEdit::AnimationTimelineEdit() {
 
 
 	panner.instantiate();
 	panner.instantiate();
 	panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_scroll_callback), callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback));
 	panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_scroll_callback), callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback));
-	panner->set_disable_rmb(true);
-	panner->set_control_scheme(ViewPanner::SCROLL_PANS);
 
 
 	set_layout_direction(Control::LAYOUT_DIRECTION_LTR);
 	set_layout_direction(Control::LAYOUT_DIRECTION_LTR);
 }
 }
@@ -4512,7 +4510,7 @@ MenuButton *AnimationTrackEditor::get_edit_menu() {
 
 
 void AnimationTrackEditor::_notification(int p_what) {
 void AnimationTrackEditor::_notification(int p_what) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
-		panner->set_control_scheme((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/animation_editors_panning_scheme").operator int());
+		panner->setup((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
 	}
 	}
 
 
 	if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
 	if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
@@ -5315,7 +5313,7 @@ void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec) {
 	scroll->set_v_scroll(scroll->get_v_scroll() - p_scroll_vec.y);
 	scroll->set_v_scroll(scroll->get_v_scroll() - p_scroll_vec.y);
 }
 }
 
 
-void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin) {
+void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
 	if (p_scroll_vec.y < 0) {
 	if (p_scroll_vec.y < 0) {
 		timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
 		timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
 	} else {
 	} else {
@@ -6111,8 +6109,6 @@ AnimationTrackEditor::AnimationTrackEditor() {
 
 
 	panner.instantiate();
 	panner.instantiate();
 	panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_scroll_callback), callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback));
 	panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_scroll_callback), callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback));
-	panner->set_disable_rmb(true);
-	panner->set_control_scheme(ViewPanner::SCROLL_PANS);
 
 
 	scroll = memnew(ScrollContainer);
 	scroll = memnew(ScrollContainer);
 	timeline_vbox->add_child(scroll);
 	timeline_vbox->add_child(scroll);
@@ -6120,7 +6116,9 @@ AnimationTrackEditor::AnimationTrackEditor() {
 	VScrollBar *sb = scroll->get_v_scroll_bar();
 	VScrollBar *sb = scroll->get_v_scroll_bar();
 	scroll->remove_child(sb);
 	scroll->remove_child(sb);
 	timeline_scroll->add_child(sb); // Move here so timeline and tracks are always aligned.
 	timeline_scroll->add_child(sb); // Move here so timeline and tracks are always aligned.
+	scroll->set_focus_mode(FOCUS_CLICK);
 	scroll->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_scroll_input));
 	scroll->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_scroll_input));
+	scroll->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
 
 
 	bezier_edit = memnew(AnimationBezierTrackEdit);
 	bezier_edit = memnew(AnimationBezierTrackEdit);
 	timeline_vbox->add_child(bezier_edit);
 	timeline_vbox->add_child(bezier_edit);

+ 2 - 2
editor/animation_track_editor.h

@@ -84,7 +84,7 @@ class AnimationTimelineEdit : public Range {
 	Ref<ViewPanner> panner;
 	Ref<ViewPanner> panner;
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
-	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	bool dragging_timeline;
 	bool dragging_timeline;
 	bool dragging_hsize;
 	bool dragging_hsize;
@@ -379,7 +379,7 @@ class AnimationTrackEditor : public VBoxContainer {
 	Ref<ViewPanner> panner;
 	Ref<ViewPanner> panner;
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
-	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	void _timeline_value_changed(double);
 	void _timeline_value_changed(double);
 
 

+ 2 - 5
editor/editor_node.cpp

@@ -6043,11 +6043,8 @@ EditorNode::EditorNode() {
 	EDITOR_DEF("interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_VHS_CIRCLE);
 	EDITOR_DEF("interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_VHS_CIRCLE);
 	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle", PROPERTY_USAGE_DEFAULT));
 	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle", PROPERTY_USAGE_DEFAULT));
 	EDITOR_DEF("run/auto_save/save_before_running", true);
 	EDITOR_DEF("run/auto_save/save_before_running", true);
-	EDITOR_DEF("interface/editors/sub_editor_panning_scheme", 0);
-	EDITOR_DEF("interface/editors/animation_editors_panning_scheme", 1);
-	// Should be in sync with ControlScheme in ViewPanner.
-	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/editors/sub_editor_panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans", PROPERTY_USAGE_DEFAULT));
-	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/editors/animation_editors_panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans", PROPERTY_USAGE_DEFAULT));
+
+	ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), Key::SPACE);
 
 
 	const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false);
 	const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false);
 	for (const String &E : textfile_ext) {
 	for (const String &E : textfile_ext) {

+ 9 - 4
editor/editor_settings.cpp

@@ -643,10 +643,15 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 	_initial_set("editors/2d/bone_outline_size", 2);
 	_initial_set("editors/2d/bone_outline_size", 2);
 	_initial_set("editors/2d/viewport_border_color", Color(0.4, 0.4, 1.0, 0.4));
 	_initial_set("editors/2d/viewport_border_color", Color(0.4, 0.4, 1.0, 0.4));
 	_initial_set("editors/2d/constrain_editor_view", true);
 	_initial_set("editors/2d/constrain_editor_view", true);
-	_initial_set("editors/2d/warped_mouse_panning", true);
-	_initial_set("editors/2d/simple_panning", false);
-	_initial_set("editors/2d/scroll_to_pan", false);
-	_initial_set("editors/2d/pan_speed", 20);
+
+	// Panning
+	// Enum should be in sync with ControlScheme in ViewPanner.
+	EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/panning/2d_editor_panning_scheme", 0, "Scroll Zooms,Scroll Pans");
+	EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/panning/sub_editor_panning_scheme", 0, "Scroll Zooms,Scroll Pans");
+	EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/panning/animation_editors_panning_scheme", 1, "Scroll Zooms,Scroll Pans");
+	_initial_set("editors/panning/simple_panning", false);
+	_initial_set("editors/panning/warped_mouse_panning", true);
+	_initial_set("editors/panning/2d_editor_pan_speed", 20);
 
 
 	// Tiles editor
 	// Tiles editor
 	_initial_set("editors/tiles_editor/display_grid", true);
 	_initial_set("editors/tiles_editor/display_grid", true);

+ 3 - 1
editor/plugins/animation_blend_tree_editor_plugin.cpp

@@ -40,6 +40,7 @@
 #include "scene/gui/menu_button.h"
 #include "scene/gui/menu_button.h"
 #include "scene/gui/panel.h"
 #include "scene/gui/panel.h"
 #include "scene/gui/progress_bar.h"
 #include "scene/gui/progress_bar.h"
+#include "scene/gui/view_panner.h"
 #include "scene/main/window.h"
 #include "scene/main/window.h"
 
 
 void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) {
 void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) {
@@ -733,7 +734,8 @@ void AnimationNodeBlendTreeEditor::_removed_from_graph() {
 
 
 void AnimationNodeBlendTreeEditor::_notification(int p_what) {
 void AnimationNodeBlendTreeEditor::_notification(int p_what) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
-		graph->set_panning_scheme((GraphEdit::PanningScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int());
+		graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
+		graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning")));
 	}
 	}
 
 
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {

+ 53 - 125
editor/plugins/canvas_item_editor_plugin.cpp

@@ -51,6 +51,7 @@
 #include "scene/gui/grid_container.h"
 #include "scene/gui/grid_container.h"
 #include "scene/gui/nine_patch_rect.h"
 #include "scene/gui/nine_patch_rect.h"
 #include "scene/gui/subviewport_container.h"
 #include "scene/gui/subviewport_container.h"
+#include "scene/gui/view_panner.h"
 #include "scene/main/canvas_layer.h"
 #include "scene/main/canvas_layer.h"
 #include "scene/main/window.h"
 #include "scene/main/window.h"
 #include "scene/resources/packed_scene.h"
 #include "scene/resources/packed_scene.h"
@@ -1116,77 +1117,14 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve
 }
 }
 
 
 bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) {
 bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) {
-	Ref<InputEventMouseButton> b = p_event;
-	if (b.is_valid() && !p_already_accepted) {
-		const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->is_ctrl_pressed();
-
-		if (pan_on_scroll) {
-			// Perform horizontal scrolling first so we can check for Shift being held.
-			if (b->is_pressed() &&
-					(b->get_button_index() == MouseButton::WHEEL_LEFT || (b->is_shift_pressed() && b->get_button_index() == MouseButton::WHEEL_UP))) {
-				// Pan left
-				view_offset.x -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
-				update_viewport();
-				return true;
-			}
-
-			if (b->is_pressed() &&
-					(b->get_button_index() == MouseButton::WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MouseButton::WHEEL_DOWN))) {
-				// Pan right
-				view_offset.x += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
-				update_viewport();
-				return true;
-			}
-		}
-
-		if (b->is_pressed() && b->get_button_index() == MouseButton::WHEEL_DOWN) {
-			// Scroll or pan down
-			if (pan_on_scroll) {
-				view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
-				update_viewport();
-			} else {
-				zoom_widget->set_zoom_by_increments(-1, Input::get_singleton()->is_key_pressed(Key::ALT));
-				if (!Math::is_equal_approx(b->get_factor(), 1.0f)) {
-					// Handle high-precision (analog) scrolling.
-					zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f));
-				}
-				_zoom_on_position(zoom_widget->get_zoom(), b->get_position());
-			}
-			return true;
-		}
-
-		if (b->is_pressed() && b->get_button_index() == MouseButton::WHEEL_UP) {
-			// Scroll or pan up
-			if (pan_on_scroll) {
-				view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
-				update_viewport();
-			} else {
-				zoom_widget->set_zoom_by_increments(1, Input::get_singleton()->is_key_pressed(Key::ALT));
-				if (!Math::is_equal_approx(b->get_factor(), 1.0f)) {
-					// Handle high-precision (analog) scrolling.
-					zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f));
-				}
-				_zoom_on_position(zoom_widget->get_zoom(), b->get_position());
-			}
-			return true;
-		}
-
-		if (!panning) {
-			if (b->is_pressed() &&
-					(b->get_button_index() == MouseButton::MIDDLE ||
-							(b->get_button_index() == MouseButton::LEFT && tool == TOOL_PAN) ||
-							(b->get_button_index() == MouseButton::LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) {
-				// Pan the viewport
-				panning = true;
-			}
-		}
+	bool panner_active = panner->gui_input(p_event, warped_panning ? viewport->get_global_rect() : Rect2());
+	if (panner->is_panning() != pan_pressed) {
+		pan_pressed = panner->is_panning();
+		_update_cursor();
+	}
 
 
-		if (panning) {
-			if (!b->is_pressed() && (pan_on_scroll || (b->get_button_index() != MouseButton::WHEEL_DOWN && b->get_button_index() != MouseButton::WHEEL_UP))) {
-				// Stop panning the viewport (for any mouse button press except zooming)
-				panning = false;
-			}
-		}
+	if (panner_active) {
+		return true;
 	}
 	}
 
 
 	Ref<InputEventKey> k = p_event;
 	Ref<InputEventKey> k = p_event;
@@ -1214,44 +1152,6 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
 				_update_zoom(16.0 * MAX(1, EDSCALE));
 				_update_zoom(16.0 * MAX(1, EDSCALE));
 			}
 			}
 		}
 		}
-
-		bool is_pan_key = pan_view_shortcut.is_valid() && pan_view_shortcut->matches_event(p_event);
-
-		if (is_pan_key && (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || drag_type != DRAG_NONE)) {
-			if (!panning) {
-				if (k->is_pressed() && !k->is_echo()) {
-					//Pan the viewport
-					panning = true;
-				}
-			} else {
-				if (!k->is_pressed()) {
-					// Stop panning the viewport (for any mouse button press)
-					panning = false;
-				}
-			}
-		}
-
-		if (is_pan_key && pan_pressed != k->is_pressed()) {
-			pan_pressed = k->is_pressed();
-			_update_cursor();
-		}
-	}
-
-	Ref<InputEventMouseMotion> m = p_event;
-	if (m.is_valid()) {
-		if (panning) {
-			// Pan the viewport
-			Point2i relative;
-			if (bool(EditorSettings::get_singleton()->get("editors/2d/warped_mouse_panning"))) {
-				relative = Input::get_singleton()->warp_mouse_motion(m, viewport->get_global_rect());
-			} else {
-				relative = m->get_relative();
-			}
-			view_offset.x -= relative.x / zoom;
-			view_offset.y -= relative.y / zoom;
-			update_viewport();
-			return true;
-		}
 	}
 	}
 
 
 	Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
 	Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
@@ -1277,7 +1177,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
 		}
 		}
 
 
 		// Pan gesture
 		// Pan gesture
-		const Vector2 delta = (int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom) * pan_gesture->get_delta();
+		const Vector2 delta = (pan_speed / zoom) * pan_gesture->get_delta();
 		view_offset.x += delta.x;
 		view_offset.x += delta.x;
 		view_offset.y += delta.y;
 		view_offset.y += delta.y;
 		update_viewport();
 		update_viewport();
@@ -1287,6 +1187,25 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
 	return false;
 	return false;
 }
 }
 
 
+void CanvasItemEditor::_scroll_callback(Vector2 p_scroll_vec) {
+	_pan_callback(-p_scroll_vec * pan_speed);
+}
+
+void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec) {
+	view_offset.x -= p_scroll_vec.x / zoom;
+	view_offset.y -= p_scroll_vec.y / zoom;
+	update_viewport();
+}
+
+void CanvasItemEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
+	zoom_widget->set_zoom_by_increments(-1, p_alt);
+	if (!Math::is_equal_approx(p_scroll_vec.y, (real_t)1.0)) {
+		// Handle high-precision (analog) scrolling.
+		zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * p_scroll_vec.y + 1.f));
+	}
+	_zoom_on_position(zoom_widget->get_zoom(), p_origin);
+}
+
 bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
 bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
 	Ref<InputEventMouseMotion> m = p_event;
 	Ref<InputEventMouseMotion> m = p_event;
 	Ref<InputEventMouseButton> b = p_event;
 	Ref<InputEventMouseButton> b = p_event;
@@ -2281,7 +2200,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
 			return true;
 			return true;
 		}
 		}
 
 
-		if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {
+		if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT && !panner->is_panning()) {
 			// Single item selection
 			// Single item selection
 			Point2 click = transform.affine_inverse().xform(b->get_position());
 			Point2 click = transform.affine_inverse().xform(b->get_position());
 
 
@@ -2498,31 +2417,34 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {
 void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
 void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
 	bool accepted = false;
 	bool accepted = false;
 
 
-	if (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || !pan_pressed) {
+	Ref<InputEventMouseButton> mb = p_event;
+	bool release_lmb = (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT); // Required to properly release some stuff (e.g. selection box) while panning.
+
+	if (EditorSettings::get_singleton()->get("editors/panning/simple_panning") || !pan_pressed || release_lmb) {
 		if ((accepted = _gui_input_rulers_and_guides(p_event))) {
 		if ((accepted = _gui_input_rulers_and_guides(p_event))) {
-			//printf("Rulers and guides\n");
+			// print_line("Rulers and guides");
 		} else if ((accepted = editor->get_editor_plugins_over()->forward_gui_input(p_event))) {
 		} else if ((accepted = editor->get_editor_plugins_over()->forward_gui_input(p_event))) {
-			//printf("Plugin\n");
+			// print_line("Plugin");
 		} else if ((accepted = _gui_input_open_scene_on_double_click(p_event))) {
 		} else if ((accepted = _gui_input_open_scene_on_double_click(p_event))) {
-			//printf("Open scene on double click\n");
+			// print_line("Open scene on double click");
 		} else if ((accepted = _gui_input_scale(p_event))) {
 		} else if ((accepted = _gui_input_scale(p_event))) {
-			//printf("Set scale\n");
+			// print_line("Set scale");
 		} else if ((accepted = _gui_input_pivot(p_event))) {
 		} else if ((accepted = _gui_input_pivot(p_event))) {
-			//printf("Set pivot\n");
+			// print_line("Set pivot");
 		} else if ((accepted = _gui_input_resize(p_event))) {
 		} else if ((accepted = _gui_input_resize(p_event))) {
-			//printf("Resize\n");
+			// print_line("Resize");
 		} else if ((accepted = _gui_input_rotate(p_event))) {
 		} else if ((accepted = _gui_input_rotate(p_event))) {
-			//printf("Rotate\n");
+			// print_line("Rotate");
 		} else if ((accepted = _gui_input_move(p_event))) {
 		} else if ((accepted = _gui_input_move(p_event))) {
-			//printf("Move\n");
+			// print_line("Move");
 		} else if ((accepted = _gui_input_anchors(p_event))) {
 		} else if ((accepted = _gui_input_anchors(p_event))) {
-			//printf("Anchors\n");
+			// print_line("Anchors");
 		} else if ((accepted = _gui_input_select(p_event))) {
 		} else if ((accepted = _gui_input_select(p_event))) {
-			//printf("Selection\n");
+			// print_line("Selection");
 		} else if ((accepted = _gui_input_ruler_tool(p_event))) {
 		} else if ((accepted = _gui_input_ruler_tool(p_event))) {
-			//printf("Measure\n");
+			// print_line("Measure");
 		} else {
 		} else {
-			//printf("Not accepted\n");
+			// print_line("Not accepted");
 		}
 		}
 	}
 	}
 
 
@@ -3935,6 +3857,10 @@ void CanvasItemEditor::_notification(int p_what) {
 		anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE);
 		anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE);
 
 
 		anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")));
 		anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")));
+
+		panner->setup((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
+		pan_speed = int(EditorSettings::get_singleton()->get("editors/panning/2d_editor_pan_speed"));
+		warped_panning = bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning"));
 	}
 	}
 
 
 	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
 	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
@@ -5205,7 +5131,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
 	dragged_guide_index = -1;
 	dragged_guide_index = -1;
 	is_hovering_h_guide = false;
 	is_hovering_h_guide = false;
 	is_hovering_v_guide = false;
 	is_hovering_v_guide = false;
-	panning = false;
 	pan_pressed = false;
 	pan_pressed = false;
 
 
 	ruler_tool_active = false;
 	ruler_tool_active = false;
@@ -5260,6 +5185,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
 	controls_vb = memnew(VBoxContainer);
 	controls_vb = memnew(VBoxContainer);
 	controls_vb->set_begin(Point2(5, 5));
 	controls_vb->set_begin(Point2(5, 5));
 
 
+	panner.instantiate();
+	panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_scroll_callback), callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback));
+
 	viewport = memnew(CanvasItemEditorViewport(p_editor, this));
 	viewport = memnew(CanvasItemEditorViewport(p_editor, this));
 	viewport_scrollable->add_child(viewport);
 	viewport_scrollable->add_child(viewport);
 	viewport->set_mouse_filter(MOUSE_FILTER_PASS);
 	viewport->set_mouse_filter(MOUSE_FILTER_PASS);
@@ -5268,6 +5196,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
 	viewport->set_focus_mode(FOCUS_ALL);
 	viewport->set_focus_mode(FOCUS_ALL);
 	viewport->connect("draw", callable_mp(this, &CanvasItemEditor::_draw_viewport));
 	viewport->connect("draw", callable_mp(this, &CanvasItemEditor::_draw_viewport));
 	viewport->connect("gui_input", callable_mp(this, &CanvasItemEditor::_gui_input_viewport));
 	viewport->connect("gui_input", callable_mp(this, &CanvasItemEditor::_gui_input_viewport));
+	viewport->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
 
 
 	h_scroll = memnew(HScrollBar);
 	h_scroll = memnew(HScrollBar);
 	viewport->add_child(h_scroll);
 	viewport->add_child(h_scroll);
@@ -5624,7 +5553,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
 
 
 	multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), Key::KP_MULTIPLY);
 	multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), Key::KP_MULTIPLY);
 	divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE);
 	divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE);
-	pan_view_shortcut = ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), Key::SPACE);
 
 
 	skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true);
 	skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true);
 	singleton = this;
 	singleton = this;

+ 8 - 2
editor/plugins/canvas_item_editor_plugin.h

@@ -42,6 +42,7 @@
 #include "scene/main/canvas_item.h"
 #include "scene/main/canvas_item.h"
 
 
 class CanvasItemEditorViewport;
 class CanvasItemEditorViewport;
+class ViewPanner;
 
 
 class CanvasItemEditorSelectedItem : public Object {
 class CanvasItemEditorSelectedItem : public Object {
 	GDCLASS(CanvasItemEditorSelectedItem, Object);
 	GDCLASS(CanvasItemEditorSelectedItem, Object);
@@ -276,7 +277,6 @@ private:
 	bool key_pos;
 	bool key_pos;
 	bool key_rot;
 	bool key_rot;
 	bool key_scale;
 	bool key_scale;
-	bool panning;
 	bool pan_pressed;
 	bool pan_pressed;
 
 
 	bool ruler_tool_active;
 	bool ruler_tool_active;
@@ -402,7 +402,13 @@ private:
 	Ref<Shortcut> set_pivot_shortcut;
 	Ref<Shortcut> set_pivot_shortcut;
 	Ref<Shortcut> multiply_grid_step_shortcut;
 	Ref<Shortcut> multiply_grid_step_shortcut;
 	Ref<Shortcut> divide_grid_step_shortcut;
 	Ref<Shortcut> divide_grid_step_shortcut;
-	Ref<Shortcut> pan_view_shortcut;
+
+	Ref<ViewPanner> panner;
+	bool warped_panning = true;
+	int pan_speed = 20;
+	void _scroll_callback(Vector2 p_scroll_vec);
+	void _pan_callback(Vector2 p_scroll_vec);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	bool _is_node_locked(const Node *p_node);
 	bool _is_node_locked(const Node *p_node);
 	bool _is_node_movable(const Node *p_node, bool p_popup_warning = false);
 	bool _is_node_movable(const Node *p_node, bool p_popup_warning = false);

+ 7 - 6
editor/plugins/polygon_2d_editor_plugin.cpp

@@ -66,7 +66,7 @@ void Polygon2DEditor::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_ENTER_TREE:
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
-			uv_panner->set_control_scheme((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int());
+			uv_panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
 		} break;
 		} break;
 		case NOTIFICATION_READY: {
 		case NOTIFICATION_READY: {
 			button_uv->set_icon(get_theme_icon(SNAME("Uv"), SNAME("EditorIcons")));
 			button_uv->set_icon(get_theme_icon(SNAME("Uv"), SNAME("EditorIcons")));
@@ -935,7 +935,7 @@ void Polygon2DEditor::_uv_pan_callback(Vector2 p_scroll_vec) {
 	uv_vscroll->set_value(uv_vscroll->get_value() - p_scroll_vec.y);
 	uv_vscroll->set_value(uv_vscroll->get_value() - p_scroll_vec.y);
 }
 }
 
 
-void Polygon2DEditor::_uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin) {
+void Polygon2DEditor::_uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
 	if (p_scroll_vec.y < 0) {
 	if (p_scroll_vec.y < 0) {
 		uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * Math::abs(p_scroll_vec.y))));
 		uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * Math::abs(p_scroll_vec.y))));
 	} else {
 	} else {
@@ -1280,10 +1280,6 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) :
 	uv_edit_mode[2]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(2));
 	uv_edit_mode[2]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(2));
 	uv_edit_mode[3]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(3));
 	uv_edit_mode[3]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(3));
 
 
-	uv_panner.instantiate();
-	uv_panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_uv_scroll_callback), callable_mp(this, &Polygon2DEditor::_uv_pan_callback), callable_mp(this, &Polygon2DEditor::_uv_zoom_callback));
-	uv_panner->set_disable_rmb(true);
-
 	uv_mode_hb->add_child(memnew(VSeparator));
 	uv_mode_hb->add_child(memnew(VSeparator));
 
 
 	uv_main_vb->add_child(uv_mode_hb);
 	uv_main_vb->add_child(uv_mode_hb);
@@ -1470,8 +1466,13 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) :
 	bone_scroll_vb = memnew(VBoxContainer);
 	bone_scroll_vb = memnew(VBoxContainer);
 	bone_scroll->add_child(bone_scroll_vb);
 	bone_scroll->add_child(bone_scroll_vb);
 
 
+	uv_panner.instantiate();
+	uv_panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_uv_scroll_callback), callable_mp(this, &Polygon2DEditor::_uv_pan_callback), callable_mp(this, &Polygon2DEditor::_uv_zoom_callback));
+
 	uv_edit_draw->connect("draw", callable_mp(this, &Polygon2DEditor::_uv_draw));
 	uv_edit_draw->connect("draw", callable_mp(this, &Polygon2DEditor::_uv_draw));
 	uv_edit_draw->connect("gui_input", callable_mp(this, &Polygon2DEditor::_uv_input));
 	uv_edit_draw->connect("gui_input", callable_mp(this, &Polygon2DEditor::_uv_input));
+	uv_edit_draw->connect("focus_exited", callable_mp(uv_panner.ptr(), &ViewPanner::release_pan_key));
+	uv_edit_draw->set_focus_mode(FOCUS_CLICK);
 	uv_draw_zoom = 1.0;
 	uv_draw_zoom = 1.0;
 	point_drag_index = -1;
 	point_drag_index = -1;
 	uv_drag = false;
 	uv_drag = false;

+ 1 - 1
editor/plugins/polygon_2d_editor_plugin.h

@@ -83,7 +83,7 @@ class Polygon2DEditor : public AbstractPolygon2DEditor {
 	Ref<ViewPanner> uv_panner;
 	Ref<ViewPanner> uv_panner;
 	void _uv_scroll_callback(Vector2 p_scroll_vec);
 	void _uv_scroll_callback(Vector2 p_scroll_vec);
 	void _uv_pan_callback(Vector2 p_scroll_vec);
 	void _uv_pan_callback(Vector2 p_scroll_vec);
-	void _uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin);
+	void _uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	VBoxContainer *bone_scroll_main_vb;
 	VBoxContainer *bone_scroll_main_vb;
 	ScrollContainer *bone_scroll;
 	ScrollContainer *bone_scroll;

+ 35 - 11
editor/plugins/texture_region_editor_plugin.cpp

@@ -35,6 +35,7 @@
 #include "core/os/keyboard.h"
 #include "core/os/keyboard.h"
 #include "editor/editor_scale.h"
 #include "editor/editor_scale.h"
 #include "scene/gui/check_box.h"
 #include "scene/gui/check_box.h"
+#include "scene/gui/view_panner.h"
 
 
 void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) {
 void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) {
 	Vector2 line = (to - from).normalized() * 10;
 	Vector2 line = (to - from).normalized() * 10;
@@ -259,6 +260,10 @@ void TextureRegionEditor::_region_draw() {
 }
 }
 
 
 void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
 void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
+	if (panner->gui_input(p_input)) {
+		return;
+	}
+
 	Transform2D mtx;
 	Transform2D mtx;
 	mtx.elements[2] = -draw_ofs * draw_zoom;
 	mtx.elements[2] = -draw_ofs * draw_zoom;
 	mtx.scale_basis(Vector2(draw_zoom, draw_zoom));
 	mtx.scale_basis(Vector2(draw_zoom, draw_zoom));
@@ -281,7 +286,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
 	Ref<InputEventMouseButton> mb = p_input;
 	Ref<InputEventMouseButton> mb = p_input;
 	if (mb.is_valid()) {
 	if (mb.is_valid()) {
 		if (mb->get_button_index() == MouseButton::LEFT) {
 		if (mb->get_button_index() == MouseButton::LEFT) {
-			if (mb->is_pressed()) {
+			if (mb->is_pressed() && !panner->is_panning()) {
 				if (node_ninepatch || obj_styleBox.is_valid()) {
 				if (node_ninepatch || obj_styleBox.is_valid()) {
 					edited_margin = -1;
 					edited_margin = -1;
 					float margins[4] = { 0 };
 					float margins[4] = { 0 };
@@ -400,7 +405,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
 					}
 					}
 				}
 				}
 
 
-			} else if (drag) {
+			} else if (!mb->is_pressed() && drag) {
 				if (edited_margin >= 0) {
 				if (edited_margin >= 0) {
 					undo_redo->create_action(TTR("Set Margin"));
 					undo_redo->create_action(TTR("Set Margin"));
 					static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };
 					static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };
@@ -461,21 +466,13 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
 					drag_index = -1;
 					drag_index = -1;
 				}
 				}
 			}
 			}
-		} else if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) {
-			_zoom_on_position(draw_zoom * ((0.95 + (0.05 * mb->get_factor())) / 0.95), mb->get_position());
-		} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) {
-			_zoom_on_position(draw_zoom * (1 - (0.05 * mb->get_factor())), mb->get_position());
 		}
 		}
 	}
 	}
 
 
 	Ref<InputEventMouseMotion> mm = p_input;
 	Ref<InputEventMouseMotion> mm = p_input;
 
 
 	if (mm.is_valid()) {
 	if (mm.is_valid()) {
-		if ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || Input::get_singleton()->is_key_pressed(Key::SPACE)) {
-			Vector2 dragged(mm->get_relative().x / draw_zoom, mm->get_relative().y / draw_zoom);
-			hscroll->set_value(hscroll->get_value() - dragged.x);
-			vscroll->set_value(vscroll->get_value() - dragged.y);
-		} else if (drag) {
+		if (drag) {
 			if (edited_margin >= 0) {
 			if (edited_margin >= 0) {
 				float new_margin = 0;
 				float new_margin = 0;
 
 
@@ -605,6 +602,24 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
 	}
 	}
 }
 }
 
 
+void TextureRegionEditor::_scroll_callback(Vector2 p_scroll_vec) {
+	_pan_callback(-p_scroll_vec * 32);
+}
+
+void TextureRegionEditor::_pan_callback(Vector2 p_scroll_vec) {
+	p_scroll_vec /= draw_zoom;
+	hscroll->set_value(hscroll->get_value() - p_scroll_vec.x);
+	vscroll->set_value(vscroll->get_value() - p_scroll_vec.y);
+}
+
+void TextureRegionEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
+	if (p_scroll_vec.y < 0) {
+		_zoom_on_position(draw_zoom * ((0.95 + (0.05 * Math::abs(p_scroll_vec.y))) / 0.95), p_origin);
+	} else {
+		_zoom_on_position(draw_zoom * (1 - (0.05 * Math::abs(p_scroll_vec.y))), p_origin);
+	}
+}
+
 void TextureRegionEditor::_scroll_changed(float) {
 void TextureRegionEditor::_scroll_changed(float) {
 	if (updating_scroll) {
 	if (updating_scroll) {
 		return;
 		return;
@@ -802,6 +817,10 @@ void TextureRegionEditor::_notification(int p_what) {
 
 
 			vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE);
 			vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE);
 			hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE);
 			hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE);
+			[[fallthrough]];
+		}
+		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+			panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
 		} break;
 		} break;
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {
 			if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {
@@ -1058,11 +1077,16 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) {
 
 
 	hb_grid->hide();
 	hb_grid->hide();
 
 
+	panner.instantiate();
+	panner->set_callbacks(callable_mp(this, &TextureRegionEditor::_scroll_callback), callable_mp(this, &TextureRegionEditor::_pan_callback), callable_mp(this, &TextureRegionEditor::_zoom_callback));
+
 	edit_draw = memnew(Panel);
 	edit_draw = memnew(Panel);
 	add_child(edit_draw);
 	add_child(edit_draw);
 	edit_draw->set_v_size_flags(SIZE_EXPAND_FILL);
 	edit_draw->set_v_size_flags(SIZE_EXPAND_FILL);
 	edit_draw->connect("draw", callable_mp(this, &TextureRegionEditor::_region_draw));
 	edit_draw->connect("draw", callable_mp(this, &TextureRegionEditor::_region_draw));
 	edit_draw->connect("gui_input", callable_mp(this, &TextureRegionEditor::_region_input));
 	edit_draw->connect("gui_input", callable_mp(this, &TextureRegionEditor::_region_input));
+	edit_draw->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
+	edit_draw->set_focus_mode(FOCUS_CLICK);
 
 
 	draw_zoom = 1.0;
 	draw_zoom = 1.0;
 	edit_draw->set_clip_contents(true);
 	edit_draw->set_clip_contents(true);

+ 7 - 0
editor/plugins/texture_region_editor_plugin.h

@@ -40,6 +40,8 @@
 #include "scene/resources/style_box.h"
 #include "scene/resources/style_box.h"
 #include "scene/resources/texture.h"
 #include "scene/resources/texture.h"
 
 
+class ViewPanner;
+
 class TextureRegionEditor : public VBoxContainer {
 class TextureRegionEditor : public VBoxContainer {
 	GDCLASS(TextureRegionEditor, VBoxContainer);
 	GDCLASS(TextureRegionEditor, VBoxContainer);
 
 
@@ -98,6 +100,11 @@ class TextureRegionEditor : public VBoxContainer {
 	Vector2 drag_from;
 	Vector2 drag_from;
 	int drag_index;
 	int drag_index;
 
 
+	Ref<ViewPanner> panner;
+	void _scroll_callback(Vector2 p_scroll_vec);
+	void _pan_callback(Vector2 p_scroll_vec);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+
 	void _set_snap_mode(int p_mode);
 	void _set_snap_mode(int p_mode);
 	void _set_snap_off_x(float p_val);
 	void _set_snap_off_x(float p_val);
 	void _set_snap_off_y(float p_val);
 	void _set_snap_off_y(float p_val);

+ 8 - 5
editor/plugins/tiles/tile_atlas_view.cpp

@@ -58,7 +58,7 @@ void TileAtlasView::_pan_callback(Vector2 p_scroll_vec) {
 	_update_zoom_and_panning(true);
 	_update_zoom_and_panning(true);
 }
 }
 
 
-void TileAtlasView::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin) {
+void TileAtlasView::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
 	zoom_widget->set_zoom_by_increments(-p_scroll_vec.y * 2);
 	zoom_widget->set_zoom_by_increments(-p_scroll_vec.y * 2);
 	emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
 	emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
 	_update_zoom_and_panning(true);
 	_update_zoom_and_panning(true);
@@ -524,7 +524,7 @@ void TileAtlasView::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_ENTER_TREE:
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED:
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED:
-			panner->set_control_scheme((ViewPanner::ControlScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int());
+			panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
 			break;
 			break;
 
 
 		case NOTIFICATION_READY:
 		case NOTIFICATION_READY:
@@ -540,9 +540,6 @@ void TileAtlasView::_bind_methods() {
 TileAtlasView::TileAtlasView() {
 TileAtlasView::TileAtlasView() {
 	set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
 	set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
 
 
-	panner.instantiate();
-	panner->set_callbacks(callable_mp(this, &TileAtlasView::_scroll_callback), callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback));
-
 	Panel *panel = memnew(Panel);
 	Panel *panel = memnew(Panel);
 	panel->set_clip_contents(true);
 	panel->set_clip_contents(true);
 	panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
 	panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
@@ -566,10 +563,16 @@ TileAtlasView::TileAtlasView() {
 	button_center_view->set_tooltip(TTR("Center View"));
 	button_center_view->set_tooltip(TTR("Center View"));
 	add_child(button_center_view);
 	add_child(button_center_view);
 
 
+	panner.instantiate();
+	panner->set_callbacks(callable_mp(this, &TileAtlasView::_scroll_callback), callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback));
+	panner->set_enable_rmb(true);
+
 	center_container = memnew(CenterContainer);
 	center_container = memnew(CenterContainer);
 	center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
 	center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
 	center_container->set_anchors_preset(Control::PRESET_CENTER);
 	center_container->set_anchors_preset(Control::PRESET_CENTER);
 	center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input));
 	center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input));
+	center_container->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
+	center_container->set_focus_mode(FOCUS_CLICK);
 	panel->add_child(center_container);
 	panel->add_child(center_container);
 
 
 	missing_source_label = memnew(Label);
 	missing_source_label = memnew(Label);

+ 1 - 1
editor/plugins/tiles/tile_atlas_view.h

@@ -69,7 +69,7 @@ private:
 	Ref<ViewPanner> panner;
 	Ref<ViewPanner> panner;
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
-	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache;
 	Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache;
 	void _update_alternative_tiles_rect_cache();
 	void _update_alternative_tiles_rect_cache();

+ 3 - 1
editor/plugins/visual_shader_editor_plugin.cpp

@@ -42,6 +42,7 @@
 #include "scene/animation/animation_player.h"
 #include "scene/animation/animation_player.h"
 #include "scene/gui/menu_button.h"
 #include "scene/gui/menu_button.h"
 #include "scene/gui/panel.h"
 #include "scene/gui/panel.h"
+#include "scene/gui/view_panner.h"
 #include "scene/main/window.h"
 #include "scene/main/window.h"
 #include "scene/resources/visual_shader_nodes.h"
 #include "scene/resources/visual_shader_nodes.h"
 #include "scene/resources/visual_shader_particle_nodes.h"
 #include "scene/resources/visual_shader_particle_nodes.h"
@@ -3222,7 +3223,8 @@ void VisualShaderEditor::_notification(int p_what) {
 	}
 	}
 
 
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
-		graph->set_panning_scheme((GraphEdit::PanningScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int());
+		graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
+		graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning")));
 	}
 	}
 
 
 	if (p_what == NOTIFICATION_DRAG_BEGIN) {
 	if (p_what == NOTIFICATION_DRAG_BEGIN) {

+ 3 - 1
modules/visual_script/editor/visual_script_editor.cpp

@@ -42,6 +42,7 @@
 #include "editor/editor_node.h"
 #include "editor/editor_node.h"
 #include "editor/editor_resource_preview.h"
 #include "editor/editor_resource_preview.h"
 #include "editor/editor_scale.h"
 #include "editor/editor_scale.h"
+#include "scene/gui/view_panner.h"
 #include "scene/main/window.h"
 #include "scene/main/window.h"
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
@@ -3782,7 +3783,8 @@ void VisualScriptEditor::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_ENTER_TREE:
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
-			graph->set_panning_scheme((GraphEdit::PanningScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int());
+			graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));
+			graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning")));
 		} break;
 		} break;
 
 
 		case NOTIFICATION_READY: {
 		case NOTIFICATION_READY: {

+ 14 - 4
scene/gui/graph_edit.cpp

@@ -1070,7 +1070,9 @@ void GraphEdit::set_selected(Node *p_child) {
 
 
 void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
 void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
 	ERR_FAIL_COND(p_ev.is_null());
 	ERR_FAIL_COND(p_ev.is_null());
-	panner->gui_input(p_ev);
+	if (panner->gui_input(p_ev, warped_panning ? get_global_rect() : Rect2())) {
+		return;
+	}
 
 
 	Ref<InputEventMouseMotion> mm = p_ev;
 	Ref<InputEventMouseMotion> mm = p_ev;
 
 
@@ -1272,7 +1274,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
 				if (_filter_input(b->get_position())) {
 				if (_filter_input(b->get_position())) {
 					return;
 					return;
 				}
 				}
-				if (Input::get_singleton()->is_key_pressed(Key::SPACE)) {
+				if (panner->is_panning()) {
 					return;
 					return;
 				}
 				}
 
 
@@ -1367,7 +1369,7 @@ void GraphEdit::_pan_callback(Vector2 p_scroll_vec) {
 	v_scroll->set_value(v_scroll->get_value() - p_scroll_vec.y);
 	v_scroll->set_value(v_scroll->get_value() - p_scroll_vec.y);
 }
 }
 
 
-void GraphEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin) {
+void GraphEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
 	set_zoom_custom(p_scroll_vec.y < 0 ? zoom * zoom_step : zoom / zoom_step, p_origin);
 	set_zoom_custom(p_scroll_vec.y < 0 ? zoom * zoom_step : zoom / zoom_step, p_origin);
 }
 }
 
 
@@ -1678,6 +1680,14 @@ HBoxContainer *GraphEdit::get_zoom_hbox() {
 	return zoom_hb;
 	return zoom_hb;
 }
 }
 
 
+Ref<ViewPanner> GraphEdit::get_panner() {
+	return panner;
+}
+
+void GraphEdit::set_warped_panning(bool p_warped) {
+	warped_panning = p_warped;
+}
+
 int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) {
 int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) {
 	switch (p_operation) {
 	switch (p_operation) {
 		case GraphEdit::IS_EQUAL: {
 		case GraphEdit::IS_EQUAL: {
@@ -2305,7 +2315,6 @@ GraphEdit::GraphEdit() {
 
 
 	panner.instantiate();
 	panner.instantiate();
 	panner->set_callbacks(callable_mp(this, &GraphEdit::_scroll_callback), callable_mp(this, &GraphEdit::_pan_callback), callable_mp(this, &GraphEdit::_zoom_callback));
 	panner->set_callbacks(callable_mp(this, &GraphEdit::_scroll_callback), callable_mp(this, &GraphEdit::_pan_callback), callable_mp(this, &GraphEdit::_zoom_callback));
-	panner->set_disable_rmb(true);
 
 
 	top_layer = memnew(GraphEditFilter(this));
 	top_layer = memnew(GraphEditFilter(this));
 	add_child(top_layer, false, INTERNAL_MODE_BACK);
 	add_child(top_layer, false, INTERNAL_MODE_BACK);
@@ -2313,6 +2322,7 @@ GraphEdit::GraphEdit() {
 	top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
 	top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
 	top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
 	top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
 	top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input));
 	top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input));
+	top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
 
 
 	connections_layer = memnew(Control);
 	connections_layer = memnew(Control);
 	add_child(connections_layer, false, INTERNAL_MODE_FRONT);
 	add_child(connections_layer, false, INTERNAL_MODE_FRONT);

+ 4 - 1
scene/gui/graph_edit.h

@@ -130,9 +130,10 @@ private:
 	float port_grab_distance_vertical;
 	float port_grab_distance_vertical;
 
 
 	Ref<ViewPanner> panner;
 	Ref<ViewPanner> panner;
+	bool warped_panning = true;
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _scroll_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
 	void _pan_callback(Vector2 p_scroll_vec);
-	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin);
+	void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
 
 
 	bool connecting = false;
 	bool connecting = false;
 	String connecting_from;
 	String connecting_from;
@@ -348,6 +349,8 @@ public:
 	bool is_connection_lines_antialiased() const;
 	bool is_connection_lines_antialiased() const;
 
 
 	HBoxContainer *get_zoom_hbox();
 	HBoxContainer *get_zoom_hbox();
+	Ref<ViewPanner> get_panner();
+	void set_warped_panning(bool p_warped);
 
 
 	void arrange_nodes();
 	void arrange_nodes();
 
 

+ 67 - 34
scene/gui/view_panner.cpp

@@ -31,22 +31,18 @@
 #include "view_panner.h"
 #include "view_panner.h"
 
 
 #include "core/input/input.h"
 #include "core/input/input.h"
+#include "core/input/shortcut.h"
 #include "core/os/keyboard.h"
 #include "core/os/keyboard.h"
 
 
 bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) {
 bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) {
 	Ref<InputEventMouseButton> mb = p_event;
 	Ref<InputEventMouseButton> mb = p_event;
 	if (mb.is_valid()) {
 	if (mb.is_valid()) {
-		// Alt modifier is unused, so ignore such events.
-		if (mb->is_alt_pressed()) {
-			return false;
-		}
-
 		Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP));
 		Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP));
 		if (scroll_vec != Vector2()) {
 		if (scroll_vec != Vector2()) {
 			if (control_scheme == SCROLL_PANS) {
 			if (control_scheme == SCROLL_PANS) {
 				if (mb->is_ctrl_pressed()) {
 				if (mb->is_ctrl_pressed()) {
 					scroll_vec.y *= mb->get_factor();
 					scroll_vec.y *= mb->get_factor();
-					callback_helper(zoom_callback, scroll_vec, mb->get_position());
+					callback_helper(zoom_callback, varray(scroll_vec, mb->get_position(), mb->is_alt_pressed()));
 					return true;
 					return true;
 				} else {
 				} else {
 					Vector2 panning;
 					Vector2 panning;
@@ -57,7 +53,7 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
 						panning.y += mb->get_factor() * scroll_vec.y;
 						panning.y += mb->get_factor() * scroll_vec.y;
 						panning.x += mb->get_factor() * scroll_vec.x;
 						panning.x += mb->get_factor() * scroll_vec.x;
 					}
 					}
-					callback_helper(scroll_callback, panning);
+					callback_helper(scroll_callback, varray(panning));
 					return true;
 					return true;
 				}
 				}
 			} else {
 			} else {
@@ -70,23 +66,28 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
 						panning.y += mb->get_factor() * scroll_vec.y;
 						panning.y += mb->get_factor() * scroll_vec.y;
 						panning.x += mb->get_factor() * scroll_vec.x;
 						panning.x += mb->get_factor() * scroll_vec.x;
 					}
 					}
-					callback_helper(scroll_callback, panning);
+					callback_helper(scroll_callback, varray(panning));
 					return true;
 					return true;
 				} else if (!mb->is_shift_pressed()) {
 				} else if (!mb->is_shift_pressed()) {
 					scroll_vec.y *= mb->get_factor();
 					scroll_vec.y *= mb->get_factor();
-					callback_helper(zoom_callback, scroll_vec, mb->get_position());
+					callback_helper(zoom_callback, varray(scroll_vec, mb->get_position(), mb->is_alt_pressed()));
 					return true;
 					return true;
 				}
 				}
 			}
 			}
 		}
 		}
 
 
-		if (mb->get_button_index() == MouseButton::MIDDLE || (mb->get_button_index() == MouseButton::RIGHT && !disable_rmb) || (mb->get_button_index() == MouseButton::LEFT && (Input::get_singleton()->is_key_pressed(Key::SPACE) || (is_dragging && !mb->is_pressed())))) {
+		// Use Alt only for scrolling.
+		if (mb->is_alt_pressed()) {
+			return false;
+		}
+
+		if (mb->get_button_index() == MouseButton::MIDDLE || (enable_rmb && mb->get_button_index() == MouseButton::RIGHT) || (!simple_panning_enabled && mb->get_button_index() == MouseButton::LEFT && is_panning())) {
 			if (mb->is_pressed()) {
 			if (mb->is_pressed()) {
 				is_dragging = true;
 				is_dragging = true;
 			} else {
 			} else {
 				is_dragging = false;
 				is_dragging = false;
 			}
 			}
-			return true;
+			return mb->get_button_index() != MouseButton::LEFT || mb->is_pressed(); // Don't consume LMB release events (it fixes some selection problems).
 		}
 		}
 	}
 	}
 
 
@@ -94,9 +95,20 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
 	if (mm.is_valid()) {
 	if (mm.is_valid()) {
 		if (is_dragging) {
 		if (is_dragging) {
 			if (p_canvas_rect != Rect2()) {
 			if (p_canvas_rect != Rect2()) {
-				callback_helper(pan_callback, Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect));
+				callback_helper(pan_callback, varray(Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect)));
 			} else {
 			} else {
-				callback_helper(pan_callback, mm->get_relative());
+				callback_helper(pan_callback, varray(mm->get_relative()));
+			}
+			return true;
+		}
+	}
+
+	Ref<InputEventKey> k = p_event;
+	if (k.is_valid()) {
+		if (pan_view_shortcut.is_valid() && pan_view_shortcut->matches_event(k)) {
+			pan_key_pressed = k->is_pressed();
+			if (simple_panning_enabled || (Input::get_singleton()->get_mouse_button_mask() & MouseButton::LEFT) != MouseButton::NONE) {
+				is_dragging = pan_key_pressed;
 			}
 			}
 			return true;
 			return true;
 		}
 		}
@@ -105,26 +117,20 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
 	return false;
 	return false;
 }
 }
 
 
-void ViewPanner::callback_helper(Callable p_callback, Vector2 p_arg1, Vector2 p_arg2) {
-	if (p_callback == zoom_callback) {
-		const Variant **argptr = (const Variant **)alloca(sizeof(Variant *) * 2);
-		Variant var1 = p_arg1;
-		argptr[0] = &var1;
-		Variant var2 = p_arg2;
-		argptr[1] = &var2;
-
-		Variant result;
-		Callable::CallError ce;
-		p_callback.call(argptr, 2, result, ce);
-	} else {
-		const Variant **argptr = (const Variant **)alloca(sizeof(Variant *));
-		Variant var = p_arg1;
-		argptr[0] = &var;
-
-		Variant result;
-		Callable::CallError ce;
-		p_callback.call(argptr, 1, result, ce);
+void ViewPanner::release_pan_key() {
+	pan_key_pressed = false;
+	is_dragging = false;
+}
+
+void ViewPanner::callback_helper(Callable p_callback, Vector<Variant> p_args) {
+	const Variant **argptr = (const Variant **)alloca(sizeof(Variant *) * p_args.size());
+	for (int i = 0; i < p_args.size(); i++) {
+		argptr[i] = &p_args[i];
 	}
 	}
+
+	Variant result;
+	Callable::CallError ce;
+	p_callback.call(argptr, p_args.size(), result, ce);
 }
 }
 
 
 void ViewPanner::set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback) {
 void ViewPanner::set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback) {
@@ -137,6 +143,33 @@ void ViewPanner::set_control_scheme(ControlScheme p_scheme) {
 	control_scheme = p_scheme;
 	control_scheme = p_scheme;
 }
 }
 
 
-void ViewPanner::set_disable_rmb(bool p_disable) {
-	disable_rmb = p_disable;
+void ViewPanner::set_enable_rmb(bool p_enable) {
+	enable_rmb = p_enable;
+}
+
+void ViewPanner::set_pan_shortcut(Ref<Shortcut> p_shortcut) {
+	pan_view_shortcut = p_shortcut;
+	pan_key_pressed = false;
+}
+
+void ViewPanner::set_simple_panning_enabled(bool p_enabled) {
+	simple_panning_enabled = p_enabled;
+}
+
+void ViewPanner::setup(ControlScheme p_scheme, Ref<Shortcut> p_shortcut, bool p_simple_panning) {
+	set_control_scheme(p_scheme);
+	set_pan_shortcut(p_shortcut);
+	set_simple_panning_enabled(p_simple_panning);
+}
+
+bool ViewPanner::is_panning() const {
+	return is_dragging || pan_key_pressed;
+}
+
+ViewPanner::ViewPanner() {
+	Array inputs;
+	inputs.append(InputEventKey::create_reference(Key::SPACE));
+
+	pan_view_shortcut.instantiate();
+	pan_view_shortcut->set_events(inputs);
 }
 }

+ 16 - 4
scene/gui/view_panner.h

@@ -34,6 +34,7 @@
 #include "core/object/ref_counted.h"
 #include "core/object/ref_counted.h"
 
 
 class InputEvent;
 class InputEvent;
+class Shortcut;
 
 
 class ViewPanner : public RefCounted {
 class ViewPanner : public RefCounted {
 	GDCLASS(ViewPanner, RefCounted);
 	GDCLASS(ViewPanner, RefCounted);
@@ -46,23 +47,34 @@ public:
 
 
 private:
 private:
 	bool is_dragging = false;
 	bool is_dragging = false;
-	bool disable_rmb = false;
+	bool pan_key_pressed = false;
+	bool enable_rmb = false;
+	bool simple_panning_enabled = false;
+
+	Ref<Shortcut> pan_view_shortcut;
 
 
 	Callable scroll_callback;
 	Callable scroll_callback;
 	Callable pan_callback;
 	Callable pan_callback;
 	Callable zoom_callback;
 	Callable zoom_callback;
 
 
-	void callback_helper(Callable p_callback, Vector2 p_arg1, Vector2 p_arg2 = Vector2());
+	void callback_helper(Callable p_callback, Vector<Variant> p_args);
 	ControlScheme control_scheme = SCROLL_ZOOMS;
 	ControlScheme control_scheme = SCROLL_ZOOMS;
 
 
 public:
 public:
 	void set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback);
 	void set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback);
 	void set_control_scheme(ControlScheme p_scheme);
 	void set_control_scheme(ControlScheme p_scheme);
-	void set_disable_rmb(bool p_disable);
+	void set_enable_rmb(bool p_enable);
+	void set_pan_shortcut(Ref<Shortcut> p_shortcut);
+	void set_simple_panning_enabled(bool p_enabled);
+
+	void setup(ControlScheme p_scheme, Ref<Shortcut> p_shortcut, bool p_simple_panning);
 
 
-	bool is_panning() const { return is_dragging; }
+	bool is_panning() const;
 
 
 	bool gui_input(const Ref<InputEvent> &p_ev, Rect2 p_canvas_rect = Rect2());
 	bool gui_input(const Ref<InputEvent> &p_ev, Rect2 p_canvas_rect = Rect2());
+	void release_pan_key();
+
+	ViewPanner();
 };
 };
 
 
 #endif // VIEW_PANNER_H
 #endif // VIEW_PANNER_H