Selaa lähdekoodia

Native pan and zoom for macOS

Bernhard Liebl 7 vuotta sitten
vanhempi
commit
80ad8afc85

+ 82 - 8
core/os/input_event.cpp

@@ -177,6 +177,14 @@ bool InputEventWithModifiers::get_command() const {
 	return command;
 }
 
+void InputEventWithModifiers::set_modifiers_from_event(const InputEventWithModifiers *event) {
+
+	set_alt(event->get_alt());
+	set_shift(event->get_shift());
+	set_control(event->get_control());
+	set_metakey(event->get_metakey());
+}
+
 void InputEventWithModifiers::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("set_alt", "enable"), &InputEventWithModifiers::set_alt);
@@ -436,10 +444,7 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
 	mb->set_id(get_id());
 	mb->set_device(get_device());
 
-	mb->set_alt(get_alt());
-	mb->set_shift(get_shift());
-	mb->set_control(get_control());
-	mb->set_metakey(get_metakey());
+	mb->set_modifiers_from_event(this);
 
 	mb->set_position(l);
 	mb->set_global_position(g);
@@ -555,10 +560,7 @@ Ref<InputEvent> InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co
 	mm->set_id(get_id());
 	mm->set_device(get_device());
 
-	mm->set_alt(get_alt());
-	mm->set_shift(get_shift());
-	mm->set_control(get_control());
-	mm->set_metakey(get_metakey());
+	mm->set_modifiers_from_event(this);
 
 	mm->set_position(l);
 	mm->set_global_position(g);
@@ -930,3 +932,75 @@ void InputEventAction::_bind_methods() {
 InputEventAction::InputEventAction() {
 	pressed = false;
 }
+/////////////////////////////
+
+void InputEventGesture::set_position(const Vector2 &p_pos) {
+
+	pos = p_pos;
+}
+
+Vector2 InputEventGesture::get_position() const {
+
+	return pos;
+}
+/////////////////////////////
+
+void InputEventMagnifyGesture::set_factor(real_t p_factor) {
+
+	factor = p_factor;
+}
+
+real_t InputEventMagnifyGesture::get_factor() const {
+
+	return factor;
+}
+
+Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs) const {
+
+	Ref<InputEventMagnifyGesture> ev;
+	ev.instance();
+
+	ev->set_id(get_id());
+	ev->set_device(get_device());
+	ev->set_modifiers_from_event(this);
+
+	ev->set_position(p_xform.xform(get_position() + p_local_ofs));
+	ev->set_factor(get_factor());
+
+	return ev;
+}
+
+InputEventMagnifyGesture::InputEventMagnifyGesture() {
+
+	factor = 1.0;
+}
+/////////////////////////////
+
+void InputEventPanGesture::set_delta(const Vector2 &p_delta) {
+
+	delta = p_delta;
+}
+
+Vector2 InputEventPanGesture::get_delta() const {
+	return delta;
+}
+
+Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs) const {
+
+	Ref<InputEventPanGesture> ev;
+	ev.instance();
+
+	ev->set_id(get_id());
+	ev->set_device(get_device());
+	ev->set_modifiers_from_event(this);
+
+	ev->set_position(p_xform.xform(get_position() + p_local_ofs));
+	ev->set_delta(get_delta());
+
+	return ev;
+}
+
+InputEventPanGesture::InputEventPanGesture() {
+
+	delta = Vector2(0, 0);
+}

+ 40 - 0
core/os/input_event.h

@@ -213,6 +213,8 @@ public:
 	void set_command(bool p_enabled);
 	bool get_command() const;
 
+	void set_modifiers_from_event(const InputEventWithModifiers *event);
+
 	InputEventWithModifiers();
 };
 
@@ -468,4 +470,42 @@ public:
 	InputEventAction();
 };
 
+class InputEventGesture : public InputEventWithModifiers {
+
+	GDCLASS(InputEventGesture, InputEventWithModifiers)
+
+	Vector2 pos;
+
+public:
+	void set_position(const Vector2 &p_pos);
+	Vector2 get_position() const;
+};
+
+class InputEventMagnifyGesture : public InputEventGesture {
+
+	GDCLASS(InputEventMagnifyGesture, InputEventGesture)
+	real_t factor;
+
+public:
+	void set_factor(real_t p_factor);
+	real_t get_factor() const;
+
+	virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const;
+
+	InputEventMagnifyGesture();
+};
+
+class InputEventPanGesture : public InputEventGesture {
+
+	GDCLASS(InputEventPanGesture, InputEventGesture)
+	Vector2 delta;
+
+public:
+	void set_delta(const Vector2 &p_delta);
+	Vector2 get_delta() const;
+
+	virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const;
+
+	InputEventPanGesture();
+};
 #endif

+ 12 - 0
editor/animation_editor.cpp

@@ -2889,6 +2889,18 @@ void AnimationKeyEditor::_track_editor_gui_input(const Ref<InputEvent> &p_input)
 			}
 		}
 	}
+
+	Ref<InputEventMagnifyGesture> magnify_gesture = p_input;
+	if (magnify_gesture.is_valid()) {
+		zoom->set_value(zoom->get_value() * magnify_gesture->get_factor());
+	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_input;
+	if (pan_gesture.is_valid()) {
+
+		h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+		v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * pan_gesture->get_delta().y / 8);
+	}
 }
 
 void AnimationKeyEditor::_notification(int p_what) {

+ 33 - 5
editor/code_editor.cpp

@@ -979,6 +979,23 @@ void CodeTextEditor::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
 		}
 	}
 
+	Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
+	if (magnify_gesture.is_valid()) {
+
+		Ref<DynamicFont> font = text_editor->get_font("font");
+
+		if (font.is_valid()) {
+			if (font->get_size() != (int)font_size) {
+				font_size = font->get_size();
+			}
+
+			font_size *= powf(magnify_gesture->get_factor(), 0.25);
+
+			_add_font_size((int)font_size - font->get_size());
+		}
+		return;
+	}
+
 	Ref<InputEventKey> k = p_event;
 
 	if (k.is_valid()) {
@@ -999,14 +1016,15 @@ void CodeTextEditor::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
 
 void CodeTextEditor::_zoom_in() {
 	font_resize_val += EDSCALE;
-
-	if (font_resize_timer->get_time_left() == 0)
-		font_resize_timer->start();
+	_zoom_changed();
 }
 
 void CodeTextEditor::_zoom_out() {
 	font_resize_val -= EDSCALE;
+	_zoom_changed();
+}
 
+void CodeTextEditor::_zoom_changed() {
 	if (font_resize_timer->get_time_left() == 0)
 		font_resize_timer->start();
 }
@@ -1067,16 +1085,25 @@ void CodeTextEditor::_complete_request() {
 
 void CodeTextEditor::_font_resize_timeout() {
 
+	if (_add_font_size(font_resize_val)) {
+		font_resize_val = 0;
+	}
+}
+
+bool CodeTextEditor::_add_font_size(int p_delta) {
+
 	Ref<DynamicFont> font = text_editor->get_font("font");
 
 	if (font.is_valid()) {
-		int new_size = CLAMP(font->get_size() + font_resize_val, 8 * EDSCALE, 96 * EDSCALE);
+		int new_size = CLAMP(font->get_size() + p_delta, 8 * EDSCALE, 96 * EDSCALE);
 		if (new_size != font->get_size()) {
 			EditorSettings::get_singleton()->set("interface/editor/source_font_size", new_size / EDSCALE);
 			font->set_size(new_size);
 		}
 
-		font_resize_val = 0;
+		return true;
+	} else {
+		return false;
 	}
 }
 
@@ -1285,6 +1312,7 @@ CodeTextEditor::CodeTextEditor() {
 	code_complete_timer->connect("timeout", this, "_code_complete_timer_timeout");
 
 	font_resize_val = 0;
+	font_size = -1;
 	font_resize_timer = memnew(Timer);
 	add_child(font_resize_timer);
 	font_resize_timer->set_one_shot(true);

+ 3 - 0
editor/code_editor.h

@@ -204,6 +204,7 @@ class CodeTextEditor : public VBoxContainer {
 
 	Timer *font_resize_timer;
 	int font_resize_val;
+	real_t font_size;
 
 	Label *error;
 
@@ -212,10 +213,12 @@ class CodeTextEditor : public VBoxContainer {
 	void _update_font();
 	void _complete_request();
 	void _font_resize_timeout();
+	bool _add_font_size(int p_delta);
 
 	void _text_editor_gui_input(const Ref<InputEvent> &p_event);
 	void _zoom_in();
 	void _zoom_out();
+	void _zoom_changed();
 	void _reset_zoom();
 
 	CodeTextEditorCodeCompleteFunc code_complete_func;

+ 16 - 0
editor/plugins/canvas_item_editor_plugin.cpp

@@ -1442,6 +1442,22 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
 		}
 	}
 
+	Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
+	if (magnify_gesture.is_valid()) {
+
+		_zoom_on_position(zoom * magnify_gesture->get_factor(), magnify_gesture->get_position());
+		return;
+	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_event;
+	if (pan_gesture.is_valid()) {
+
+		const Vector2 delta = (int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom) * pan_gesture->get_delta();
+		h_scroll->set_value(h_scroll->get_value() + delta.x);
+		v_scroll->set_value(v_scroll->get_value() + delta.y);
+		return;
+	}
+
 	Ref<InputEventMouseButton> b = p_event;
 	if (b.is_valid()) {
 		// Button event

+ 13 - 0
editor/plugins/polygon_2d_editor_plugin.cpp

@@ -339,6 +339,19 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
 			uv_edit_draw->update();
 		}
 	}
+
+	Ref<InputEventMagnifyGesture> magnify_gesture = p_input;
+	if (magnify_gesture.is_valid()) {
+
+		uv_zoom->set_value(uv_zoom->get_value() * magnify_gesture->get_factor());
+	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_input;
+	if (pan_gesture.is_valid()) {
+
+		uv_hscroll->set_value(uv_hscroll->get_value() + uv_hscroll->get_page() * pan_gesture->get_delta().x / 8);
+		uv_vscroll->set_value(uv_vscroll->get_value() + uv_vscroll->get_page() * pan_gesture->get_delta().y / 8);
+	}
 }
 
 void Polygon2DEditor::_uv_scroll_changed(float) {

+ 145 - 71
editor/plugins/spatial_editor_plugin.cpp

@@ -1694,92 +1694,78 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 
 		switch (nav_mode) {
 			case NAVIGATION_PAN: {
+				_nav_pan(m, _get_warped_mouse_motion(m));
 
-				real_t pan_speed = 1 / 150.0;
-				int pan_speed_modifier = 10;
-				if (nav_scheme == NAVIGATION_MAYA && m->get_shift())
-					pan_speed *= pan_speed_modifier;
+			} break;
 
-				Point2i relative = _get_warped_mouse_motion(m);
+			case NAVIGATION_ZOOM: {
+				_nav_zoom(m, m->get_relative());
 
-				Transform camera_transform;
+			} break;
 
-				camera_transform.translate(cursor.pos);
-				camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
-				camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
-				Vector3 translation(-relative.x * pan_speed, relative.y * pan_speed, 0);
-				translation *= cursor.distance / DISTANCE_DEFAULT;
-				camera_transform.translate(translation);
-				cursor.pos = camera_transform.origin;
+			case NAVIGATION_ORBIT: {
+				_nav_orbit(m, _get_warped_mouse_motion(m));
+
+			} break;
+
+			case NAVIGATION_LOOK: {
+				_nav_look(m, _get_warped_mouse_motion(m));
+
+			} break;
+
+			default: {}
+		}
+	}
+
+	Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
+	if (magnify_gesture.is_valid()) {
+
+		if (is_freelook_active())
+			scale_freelook_speed(magnify_gesture->get_factor());
+		else
+			scale_cursor_distance(1.0 / magnify_gesture->get_factor());
+	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_event;
+	if (pan_gesture.is_valid()) {
+
+		NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
+		NavigationMode nav_mode = NAVIGATION_NONE;
+
+		if (nav_scheme == NAVIGATION_GODOT) {
+
+			int mod = _get_key_modifier(pan_gesture);
+
+			if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier"))
+				nav_mode = NAVIGATION_PAN;
+			else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier"))
+				nav_mode = NAVIGATION_ZOOM;
+			else if (mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier"))
+				nav_mode = NAVIGATION_ORBIT;
+
+		} else if (nav_scheme == NAVIGATION_MAYA) {
+			if (pan_gesture->get_alt())
+				nav_mode = NAVIGATION_PAN;
+		}
+
+		switch (nav_mode) {
+			case NAVIGATION_PAN: {
+				_nav_pan(m, pan_gesture->get_delta());
 
 			} break;
 
 			case NAVIGATION_ZOOM: {
-				real_t zoom_speed = 1 / 80.0;
-				int zoom_speed_modifier = 10;
-				if (nav_scheme == NAVIGATION_MAYA && m->get_shift())
-					zoom_speed *= zoom_speed_modifier;
-
-				NavigationZoomStyle zoom_style = (NavigationZoomStyle)EditorSettings::get_singleton()->get("editors/3d/navigation/zoom_style").operator int();
-				if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) {
-					if (m->get_relative().x > 0)
-						scale_cursor_distance(1 - m->get_relative().x * zoom_speed);
-					else if (m->get_relative().x < 0)
-						scale_cursor_distance(1.0 / (1 + m->get_relative().x * zoom_speed));
-				} else {
-					if (m->get_relative().y > 0)
-						scale_cursor_distance(1 + m->get_relative().y * zoom_speed);
-					else if (m->get_relative().y < 0)
-						scale_cursor_distance(1.0 / (1 - m->get_relative().y * zoom_speed));
-				}
+				_nav_zoom(m, pan_gesture->get_delta());
 
 			} break;
 
 			case NAVIGATION_ORBIT: {
-				Point2i relative = _get_warped_mouse_motion(m);
-
-				real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity");
-				real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel);
-
-				cursor.x_rot += relative.y * radians_per_pixel;
-				cursor.y_rot += relative.x * radians_per_pixel;
-				if (cursor.x_rot > Math_PI / 2.0)
-					cursor.x_rot = Math_PI / 2.0;
-				if (cursor.x_rot < -Math_PI / 2.0)
-					cursor.x_rot = -Math_PI / 2.0;
-				name = "";
-				_update_name();
+				_nav_orbit(m, pan_gesture->get_delta());
+
 			} break;
 
 			case NAVIGATION_LOOK: {
-				// Freelook only works properly in perspective.
-				// It technically works too in ortho, but it's awful for a user due to fov being near zero
-				if (!orthogonal) {
-					Point2i relative = _get_warped_mouse_motion(m);
-
-					real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity");
-					real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel);
-
-					// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".
-					Transform prev_camera_transform = to_camera_transform(cursor);
-
-					cursor.x_rot += relative.y * radians_per_pixel;
-					cursor.y_rot += relative.x * radians_per_pixel;
-					if (cursor.x_rot > Math_PI / 2.0)
-						cursor.x_rot = Math_PI / 2.0;
-					if (cursor.x_rot < -Math_PI / 2.0)
-						cursor.x_rot = -Math_PI / 2.0;
-
-					// Look is like the opposite of Orbit: the focus point rotates around the camera
-					Transform camera_transform = to_camera_transform(cursor);
-					Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
-					Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
-					Vector3 diff = prev_pos - pos;
-					cursor.pos += diff;
-
-					name = "";
-					_update_name();
-				}
+				_nav_look(m, pan_gesture->get_delta());
 
 			} break;
 
@@ -1885,6 +1871,94 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 		accept_event();
 }
 
+void SpatialEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
+
+	const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
+
+	real_t pan_speed = 1 / 150.0;
+	int pan_speed_modifier = 10;
+	if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift())
+		pan_speed *= pan_speed_modifier;
+
+	Transform camera_transform;
+
+	camera_transform.translate(cursor.pos);
+	camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
+	camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
+	Vector3 translation(-p_relative.x * pan_speed, p_relative.y * pan_speed, 0);
+	translation *= cursor.distance / DISTANCE_DEFAULT;
+	camera_transform.translate(translation);
+	cursor.pos = camera_transform.origin;
+}
+
+void SpatialEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
+
+	const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
+
+	real_t zoom_speed = 1 / 80.0;
+	int zoom_speed_modifier = 10;
+	if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift())
+		zoom_speed *= zoom_speed_modifier;
+
+	NavigationZoomStyle zoom_style = (NavigationZoomStyle)EditorSettings::get_singleton()->get("editors/3d/navigation/zoom_style").operator int();
+	if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) {
+		if (p_relative.x > 0)
+			scale_cursor_distance(1 - p_relative.x * zoom_speed);
+		else if (p_relative.x < 0)
+			scale_cursor_distance(1.0 / (1 + p_relative.x * zoom_speed));
+	} else {
+		if (p_relative.y > 0)
+			scale_cursor_distance(1 + p_relative.y * zoom_speed);
+		else if (p_relative.y < 0)
+			scale_cursor_distance(1.0 / (1 - p_relative.y * zoom_speed));
+	}
+}
+
+void SpatialEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
+
+	real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity");
+	real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel);
+
+	cursor.x_rot += p_relative.y * radians_per_pixel;
+	cursor.y_rot += p_relative.x * radians_per_pixel;
+	if (cursor.x_rot > Math_PI / 2.0)
+		cursor.x_rot = Math_PI / 2.0;
+	if (cursor.x_rot < -Math_PI / 2.0)
+		cursor.x_rot = -Math_PI / 2.0;
+	name = "";
+	_update_name();
+}
+
+void SpatialEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
+
+	// Freelook only works properly in perspective.
+	// It technically works too in ortho, but it's awful for a user due to fov being near zero
+	if (!orthogonal) {
+		real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity");
+		real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel);
+
+		// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".
+		Transform prev_camera_transform = to_camera_transform(cursor);
+
+		cursor.x_rot += p_relative.y * radians_per_pixel;
+		cursor.y_rot += p_relative.x * radians_per_pixel;
+		if (cursor.x_rot > Math_PI / 2.0)
+			cursor.x_rot = Math_PI / 2.0;
+		if (cursor.x_rot < -Math_PI / 2.0)
+			cursor.x_rot = -Math_PI / 2.0;
+
+		// Look is like the opposite of Orbit: the focus point rotates around the camera
+		Transform camera_transform = to_camera_transform(cursor);
+		Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
+		Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
+		Vector3 diff = prev_pos - pos;
+		cursor.pos += diff;
+
+		name = "";
+		_update_name();
+	}
+}
+
 void SpatialEditorViewport::set_freelook_active(bool active_now) {
 
 	if (!freelook_active && active_now) {

+ 5 - 0
editor/plugins/spatial_editor_plugin.h

@@ -170,6 +170,11 @@ private:
 	void _select_region();
 	bool _gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only = false);
 
+	void _nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
+	void _nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
+	void _nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
+	void _nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
+
 	float get_znear() const;
 	float get_zfar() const;
 	float get_fov() const;

+ 9 - 0
main/input_default.cpp

@@ -319,6 +319,15 @@ void InputDefault::parse_input_event(const Ref<InputEvent> &p_event) {
 		set_joy_axis(jm->get_device(), jm->get_axis(), jm->get_axis_value());
 	}
 
+	Ref<InputEventGesture> ge = p_event;
+
+	if (ge.is_valid()) {
+
+		if (main_loop) {
+			main_loop->input_event(ge);
+		}
+	}
+
 	if (!p_event->is_echo()) {
 		for (const Map<StringName, InputMap::Action>::Element *E = InputMap::get_singleton()->get_action_map().front(); E; E = E->next()) {
 

+ 10 - 0
modules/gridmap/grid_map_editor_plugin.cpp

@@ -623,6 +623,16 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu
 		return do_input_action(p_camera, mm->get_position(), false);
 	}
 
+	Ref<InputEventPanGesture> pan_gesture = p_event;
+	if (pan_gesture.is_valid()) {
+
+		if (pan_gesture->get_command() || pan_gesture->get_shift()) {
+			const real_t delta = pan_gesture->get_delta().y;
+			floor->set_value(floor->get_value() + SGN(delta));
+			return true;
+		}
+	}
+
 	return false;
 }
 

+ 43 - 11
platform/osx/os_osx.mm

@@ -85,6 +85,15 @@ static int prev_mouse_y = 0;
 static int button_mask = 0;
 static bool mouse_down_control = false;
 
+static Vector2 get_mouse_pos(NSEvent *event) {
+
+	const NSRect contentRect = [OS_OSX::singleton->window_view frame];
+	const NSPoint p = [event locationInWindow];
+	mouse_x = p.x * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]);
+	mouse_y = (contentRect.size.height - p.y) * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]);
+	return Vector2(mouse_x, mouse_y);
+}
+
 @interface GodotApplication : NSApplication
 @end
 
@@ -508,12 +517,9 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) {
 	mm->set_button_mask(button_mask);
 	prev_mouse_x = mouse_x;
 	prev_mouse_y = mouse_y;
-	const NSRect contentRect = [OS_OSX::singleton->window_view frame];
-	const NSPoint p = [event locationInWindow];
-	mouse_x = p.x * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]);
-	mouse_y = (contentRect.size.height - p.y) * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]);
-	mm->set_position(Vector2(mouse_x, mouse_y));
-	mm->set_global_position(Vector2(mouse_x, mouse_y));
+	const Vector2 pos = get_mouse_pos(event);
+	mm->set_position(pos);
+	mm->set_global_position(pos);
 	Vector2 relativeMotion = Vector2();
 	relativeMotion.x = [event deltaX] * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]);
 	relativeMotion.y = [event deltaY] * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]);
@@ -575,6 +581,15 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) {
 		OS_OSX::singleton->input->set_mouse_in_window(true);
 }
 
+- (void)magnifyWithEvent:(NSEvent *)event {
+	Ref<InputEventMagnifyGesture> ev;
+	ev.instance();
+	get_key_modifier_state([event modifierFlags], ev);
+	ev->set_position(get_mouse_pos(event));
+	ev->set_factor([event magnification] + 1.0);
+	OS_OSX::singleton->push_input(ev);
+}
+
 - (void)viewDidChangeBackingProperties {
 	// nothing left to do here
 }
@@ -838,6 +853,18 @@ inline void sendScrollEvent(int button, double factor, int modifierFlags) {
 	OS_OSX::singleton->push_input(sc);
 }
 
+inline void sendPanEvent(double dx, double dy, int modifierFlags) {
+
+	Ref<InputEventPanGesture> pg;
+	pg.instance();
+
+	get_key_modifier_state(modifierFlags, pg);
+	Vector2 mouse_pos = Vector2(mouse_x, mouse_y);
+	pg->set_position(mouse_pos);
+	pg->set_delta(Vector2(-dx, -dy));
+	OS_OSX::singleton->push_input(pg);
+}
+
 - (void)scrollWheel:(NSEvent *)event {
 	double deltaX, deltaY;
 
@@ -856,11 +883,16 @@ inline void sendScrollEvent(int button, double factor, int modifierFlags) {
 		deltaX = [event deltaX];
 		deltaY = [event deltaY];
 	}
-	if (fabs(deltaX)) {
-		sendScrollEvent(0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]);
-	}
-	if (fabs(deltaY)) {
-		sendScrollEvent(0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]);
+
+	if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
+		sendPanEvent(deltaX, deltaY, [event modifierFlags]);
+	} else {
+		if (fabs(deltaX)) {
+			sendScrollEvent(0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]);
+		}
+		if (fabs(deltaY)) {
+			sendScrollEvent(0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]);
+		}
 	}
 }
 

+ 20 - 2
scene/gui/graph_edit.cpp

@@ -964,6 +964,19 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
 		emit_signal("delete_nodes_request");
 		accept_event();
 	}
+
+	Ref<InputEventMagnifyGesture> magnify_gesture = p_ev;
+	if (magnify_gesture.is_valid()) {
+
+		set_zoom_custom(zoom * magnify_gesture->get_factor(), magnify_gesture->get_position());
+	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_ev;
+	if (pan_gesture.is_valid()) {
+
+		h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+		v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
+	}
 }
 
 void GraphEdit::clear_connections() {
@@ -975,6 +988,11 @@ void GraphEdit::clear_connections() {
 
 void GraphEdit::set_zoom(float p_zoom) {
 
+	set_zoom_custom(p_zoom, get_size() / 2);
+}
+
+void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) {
+
 	p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
 	if (zoom == p_zoom)
 		return;
@@ -982,7 +1000,7 @@ void GraphEdit::set_zoom(float p_zoom) {
 	zoom_minus->set_disabled(zoom == MIN_ZOOM);
 	zoom_plus->set_disabled(zoom == MAX_ZOOM);
 
-	Vector2 sbofs = (Vector2(h_scroll->get_value(), v_scroll->get_value()) + get_size() / 2) / zoom;
+	Vector2 sbofs = (Vector2(h_scroll->get_value(), v_scroll->get_value()) + p_center) / zoom;
 
 	zoom = p_zoom;
 	top_layer->update();
@@ -992,7 +1010,7 @@ void GraphEdit::set_zoom(float p_zoom) {
 
 	if (is_visible_in_tree()) {
 
-		Vector2 ofs = sbofs * zoom - get_size() / 2;
+		Vector2 ofs = sbofs * zoom - p_center;
 		h_scroll->set_value(ofs.x);
 		v_scroll->set_value(ofs.y);
 	}

+ 1 - 0
scene/gui/graph_edit.h

@@ -179,6 +179,7 @@ public:
 	bool is_valid_connection_type(int p_type, int p_with_type) const;
 
 	void set_zoom(float p_zoom);
+	void set_zoom_custom(float p_zoom, const Vector2 &p_center);
 	float get_zoom() const;
 
 	GraphEditFilter *get_top_layer() const { return top_layer; }

+ 6 - 0
scene/gui/item_list.cpp

@@ -713,6 +713,12 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
 			}
 		}
 	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_event;
+	if (pan_gesture.is_valid()) {
+
+		scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * pan_gesture->get_delta().y / 8);
+	}
 }
 
 void ItemList::ensure_current_is_visible() {

+ 10 - 0
scene/gui/rich_text_label.cpp

@@ -817,6 +817,16 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
 		}
 	}
 
+	Ref<InputEventPanGesture> pan_gesture = p_event;
+	if (pan_gesture.is_valid()) {
+
+		if (scroll_active)
+
+			vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8);
+
+		return;
+	}
+
 	Ref<InputEventKey> k = p_event;
 
 	if (k.is_valid()) {

+ 11 - 0
scene/gui/scroll_container.cpp

@@ -180,6 +180,17 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) {
 			time_since_motion = 0;
 		}
 	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_gui_input;
+	if (pan_gesture.is_valid()) {
+
+		if (h_scroll->is_visible_in_tree()) {
+			h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+		}
+		if (v_scroll->is_visible_in_tree()) {
+			v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
+		}
+	}
 }
 
 void ScrollContainer::_update_scrollbar_position() {

+ 59 - 38
scene/gui/text_edit.cpp

@@ -1777,46 +1777,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 		if (mb->is_pressed()) {
 
 			if (mb->get_button_index() == BUTTON_WHEEL_UP && !mb->get_command()) {
-				float scroll_factor = 3 * mb->get_factor();
-				if (scrolling) {
-					target_v_scroll = (target_v_scroll - scroll_factor);
-				} else {
-					target_v_scroll = (v_scroll->get_value() - scroll_factor);
-				}
-
-				if (smooth_scroll_enabled) {
-					if (target_v_scroll <= 0) {
-						target_v_scroll = 0;
-					}
-					scrolling = true;
-					set_physics_process(true);
-				} else {
-					v_scroll->set_value(target_v_scroll);
-				}
+				_scroll_up(3 * mb->get_factor());
 			}
 			if (mb->get_button_index() == BUTTON_WHEEL_DOWN && !mb->get_command()) {
-				float scroll_factor = 3 * mb->get_factor();
-				if (scrolling) {
-					target_v_scroll = (target_v_scroll + scroll_factor);
-				} else {
-					target_v_scroll = (v_scroll->get_value() + scroll_factor);
-				}
-
-				if (smooth_scroll_enabled) {
-					int max_v_scroll = get_total_unhidden_rows();
-					if (!scroll_past_end_of_file_enabled) {
-						max_v_scroll -= get_visible_rows();
-						max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows());
-					}
-
-					if (target_v_scroll > max_v_scroll) {
-						target_v_scroll = max_v_scroll;
-					}
-					scrolling = true;
-					set_physics_process(true);
-				} else {
-					v_scroll->set_value(target_v_scroll);
-				}
+				_scroll_down(3 * mb->get_factor());
 			}
 			if (mb->get_button_index() == BUTTON_WHEEL_LEFT) {
 				h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
@@ -1973,6 +1937,19 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 		}
 	}
 
+	const Ref<InputEventPanGesture> pan_gesture = p_gui_input;
+	if (pan_gesture.is_valid()) {
+
+		const real_t delta = pan_gesture->get_delta().y;
+		if (delta < 0) {
+			_scroll_up(-delta);
+		} else {
+			_scroll_down(delta);
+		}
+		h_scroll->set_value(h_scroll->get_value() + pan_gesture->get_delta().x * 100);
+		return;
+	}
+
 	Ref<InputEventMouseMotion> mm = p_gui_input;
 
 	if (mm.is_valid()) {
@@ -3066,6 +3043,50 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 	}
 }
 
+void TextEdit::_scroll_up(real_t p_delta) {
+
+	if (scrolling) {
+		target_v_scroll = (target_v_scroll - p_delta);
+	} else {
+		target_v_scroll = (v_scroll->get_value() - p_delta);
+	}
+
+	if (smooth_scroll_enabled) {
+		if (target_v_scroll <= 0) {
+			target_v_scroll = 0;
+		}
+		scrolling = true;
+		set_physics_process(true);
+	} else {
+		v_scroll->set_value(target_v_scroll);
+	}
+}
+
+void TextEdit::_scroll_down(real_t p_delta) {
+
+	if (scrolling) {
+		target_v_scroll = (target_v_scroll + p_delta);
+	} else {
+		target_v_scroll = (v_scroll->get_value() + p_delta);
+	}
+
+	if (smooth_scroll_enabled) {
+		int max_v_scroll = get_total_unhidden_rows();
+		if (!scroll_past_end_of_file_enabled) {
+			max_v_scroll -= get_visible_rows();
+			max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows());
+		}
+
+		if (target_v_scroll > max_v_scroll) {
+			target_v_scroll = max_v_scroll;
+		}
+		scrolling = true;
+		set_physics_process(true);
+	} else {
+		v_scroll->set_value(target_v_scroll);
+	}
+}
+
 void TextEdit::_pre_shift_selection() {
 
 	if (!selection.active || selection.selecting_mode == Selection::MODE_NONE) {

+ 3 - 0
scene/gui/text_edit.h

@@ -328,6 +328,9 @@ class TextEdit : public Control {
 	void _update_selection_mode_word();
 	void _update_selection_mode_line();
 
+	void _scroll_up(real_t p_delta);
+	void _scroll_down(real_t p_delta);
+
 	void _pre_shift_selection();
 	void _post_shift_selection();
 

+ 6 - 0
scene/gui/tree.cpp

@@ -2612,6 +2612,12 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
 			} break;
 		}
 	}
+
+	Ref<InputEventPanGesture> pan_gesture = p_event;
+	if (pan_gesture.is_valid()) {
+
+		v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
+	}
 }
 
 bool Tree::edit_selected() {

+ 24 - 0
scene/main/viewport.cpp

@@ -2013,6 +2013,30 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
 		}
 	}
 
+	Ref<InputEventGesture> gesture_event = p_event;
+	if (gesture_event.is_valid()) {
+
+		Size2 pos = gesture_event->get_position();
+
+		Control *over = _gui_find_control(pos);
+		if (over) {
+
+			if (over->can_process()) {
+
+				gesture_event = gesture_event->xformed_by(Transform2D()); //make a copy
+				if (over == gui.mouse_focus) {
+					pos = gui.focus_inv_xform.xform(pos);
+				} else {
+					pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos);
+				}
+				gesture_event->set_position(pos);
+				_gui_call_input(over, gesture_event);
+			}
+			get_tree()->set_input_as_handled();
+			return;
+		}
+	}
+
 	Ref<InputEventScreenDrag> drag_event = p_event;
 	if (drag_event.is_valid()) {