Browse Source

Merge pull request #80334 from Sauermann/fix-window-out-of-viewport-events

Fix nodes receiving mouse events in black bars of `Window`
Rémi Verschelde 2 years ago
parent
commit
a7dc4c22a9

+ 4 - 4
doc/classes/Node.xml

@@ -1022,12 +1022,12 @@
 			Notification received right after the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
 		</constant>
 		<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
-			Notification received from the OS when the mouse enters the game window.
-			Implemented on desktop and web platforms.
+			Notification received when the mouse enters the window.
+			Implemented for embedded windows and on desktop and web platforms.
 		</constant>
 		<constant name="NOTIFICATION_WM_MOUSE_EXIT" value="1003">
-			Notification received from the OS when the mouse leaves the game window.
-			Implemented on desktop and web platforms.
+			Notification received when the mouse leaves the window.
+			Implemented for embedded windows and on desktop and web platforms.
 		</constant>
 		<constant name="NOTIFICATION_WM_WINDOW_FOCUS_IN" value="1004">
 			Notification received when the node's parent [Window] is focused. This may be a change of focus between two windows of the same engine instance, or from the OS desktop or a third-party application to a window of the game (in which case [constant NOTIFICATION_APPLICATION_FOCUS_IN] is also emitted).

+ 2 - 2
scene/main/viewport.cpp

@@ -2955,7 +2955,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
 
 void Viewport::_update_mouse_over() {
 	// Update gui.mouse_over and gui.subwindow_over in all Viewports.
-	// Send necessary mouse_enter/mouse_exit signals and the NOTIFICATION_VP_MOUSE_ENTER/NOTIFICATION_VP_MOUSE_EXIT notifications for every Viewport in the SceneTree.
+	// Send necessary mouse_enter/mouse_exit signals and the MOUSE_ENTER/MOUSE_EXIT notifications for every Viewport in the SceneTree.
 
 	if (is_attached_in_viewport()) {
 		// Execute this function only, when it is processed by a native Window or a SubViewport, that has no SubViewportContainer as parent.
@@ -3009,7 +3009,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
 						}
 						gui.subwindow_over = sw;
 						if (!sw->is_input_disabled()) {
-							sw->notification(NOTIFICATION_VP_MOUSE_ENTER);
+							sw->_propagate_window_notification(sw, NOTIFICATION_WM_MOUSE_ENTER);
 						}
 					}
 					if (!sw->is_input_disabled()) {

+ 2 - 3
scene/main/viewport.h

@@ -468,7 +468,8 @@ private:
 	SubWindowResize _sub_window_get_resize_margin(Window *p_subwindow, const Point2 &p_point);
 
 	void _update_mouse_over();
-	void _update_mouse_over(Vector2 p_pos);
+	virtual void _update_mouse_over(Vector2 p_pos);
+	virtual void _mouse_leave_viewport();
 
 	virtual bool _can_consume_input_events() const { return true; }
 	uint64_t event_count = 0;
@@ -482,8 +483,6 @@ protected:
 	Size2i _get_size_2d_override() const;
 	bool _is_size_allocated() const;
 
-	void _mouse_leave_viewport();
-
 	void _notification(int p_what);
 	void _process_picking();
 	static void _bind_methods();

+ 37 - 1
scene/main/window.cpp

@@ -679,7 +679,7 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
 			}
 			_propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER);
 			root->gui.windowmanager_window_over = this;
-			notification(NOTIFICATION_VP_MOUSE_ENTER);
+			mouse_in_window = true;
 			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE)) {
 				DisplayServer::get_singleton()->cursor_set_shape(DisplayServer::CURSOR_ARROW); //restore cursor shape
 			}
@@ -692,6 +692,7 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
 #endif // DEV_ENABLED
 				return;
 			}
+			mouse_in_window = false;
 			root->gui.windowmanager_window_over->_mouse_leave_viewport();
 			root->gui.windowmanager_window_over = nullptr;
 			_propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT);
@@ -2552,6 +2553,41 @@ bool Window::is_attached_in_viewport() const {
 	return get_embedder();
 }
 
+void Window::_update_mouse_over(Vector2 p_pos) {
+	if (!mouse_in_window) {
+		if (is_embedded()) {
+			mouse_in_window = true;
+			_propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER);
+		} else {
+			// Prevent update based on delayed InputEvents from DisplayServer.
+			return;
+		}
+	}
+
+	bool new_in = get_visible_rect().has_point(p_pos);
+	if (new_in == gui.mouse_in_viewport) {
+		if (new_in) {
+			Viewport::_update_mouse_over(p_pos);
+		}
+		return;
+	}
+
+	if (new_in) {
+		notification(NOTIFICATION_VP_MOUSE_ENTER);
+		Viewport::_update_mouse_over(p_pos);
+	} else {
+		Viewport::_mouse_leave_viewport();
+	}
+}
+
+void Window::_mouse_leave_viewport() {
+	Viewport::_mouse_leave_viewport();
+	if (is_embedded()) {
+		mouse_in_window = false;
+		_propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT);
+	}
+}
+
 void Window::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_title", "title"), &Window::set_title);
 	ClassDB::bind_method(D_METHOD("get_title"), &Window::get_title);

+ 4 - 0
scene/main/window.h

@@ -203,6 +203,10 @@ private:
 	void _event_callback(DisplayServer::WindowEvent p_event);
 	virtual bool _can_consume_input_events() const override;
 
+	bool mouse_in_window = false;
+	void _update_mouse_over(Vector2 p_pos) override;
+	void _mouse_leave_viewport() override;
+
 	Ref<Shortcut> debugger_stop_shortcut;
 
 protected:

+ 96 - 0
tests/scene/test_window.h

@@ -0,0 +1,96 @@
+/**************************************************************************/
+/*  test_window.h                                                         */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef TEST_WINDOW_H
+#define TEST_WINDOW_H
+
+#include "scene/gui/control.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestWindow {
+
+class NotificationControl : public Control {
+	GDCLASS(NotificationControl, Control);
+
+protected:
+	void _notification(int p_what) {
+		switch (p_what) {
+			case NOTIFICATION_MOUSE_ENTER: {
+				mouse_over = true;
+			} break;
+
+			case NOTIFICATION_MOUSE_EXIT: {
+				mouse_over = false;
+			} break;
+		}
+	}
+
+public:
+	bool mouse_over = false;
+};
+
+TEST_CASE("[SceneTree][Window]") {
+	Window *root = SceneTree::get_singleton()->get_root();
+
+	SUBCASE("Control-mouse-over within Window-black bars should not happen") {
+		Window *w = memnew(Window);
+		root->add_child(w);
+		w->set_size(Size2i(400, 200));
+		w->set_position(Size2i(0, 0));
+		w->set_content_scale_size(Size2i(200, 200));
+		w->set_content_scale_mode(Window::CONTENT_SCALE_MODE_CANVAS_ITEMS);
+		w->set_content_scale_aspect(Window::CONTENT_SCALE_ASPECT_KEEP);
+		NotificationControl *c = memnew(NotificationControl);
+		w->add_child(c);
+		c->set_size(Size2i(100, 100));
+		c->set_position(Size2i(-50, -50));
+
+		CHECK_FALSE(c->mouse_over);
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2i(110, 10), MouseButtonMask::NONE, Key::NONE);
+		CHECK(c->mouse_over);
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2i(90, 10), MouseButtonMask::NONE, Key::NONE);
+		CHECK_FALSE(c->mouse_over); // GH-80011
+
+		/* TODO:
+		SEND_GUI_MOUSE_BUTTON_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+		SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+		CHECK(Control was not pressed);
+		*/
+
+		memdelete(c);
+		memdelete(w);
+	}
+}
+
+} // namespace TestWindow
+
+#endif // TEST_WINDOW_H

+ 1 - 0
tests/test_main.cpp

@@ -115,6 +115,7 @@
 #include "tests/scene/test_theme.h"
 #include "tests/scene/test_viewport.h"
 #include "tests/scene/test_visual_shader.h"
+#include "tests/scene/test_window.h"
 #include "tests/servers/rendering/test_shader_preprocessor.h"
 #include "tests/servers/test_navigation_server_2d.h"
 #include "tests/servers/test_navigation_server_3d.h"