diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 49ab3918bb09..7510d6bb6409 100644 --- a/doc/classes/Node.xml +++ b/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. - 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. - 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. 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). diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 2d3aa66f2cd4..180efaaa603c 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2955,7 +2955,7 @@ bool Viewport::_sub_windows_forward_input(const Ref &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()) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 331ce98cdd1f..1e107ea99c6e 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -468,7 +468,8 @@ class Viewport : public Node { 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 @@ class Viewport : public Node { 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(); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 875b53203a20..422bca364599 100644 --- a/scene/main/window.cpp +++ b/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); @@ -2519,6 +2520,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); diff --git a/scene/main/window.h b/scene/main/window.h index 18ddd896625f..173ec388e9f9 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -197,6 +197,10 @@ class Window : public Viewport { 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 debugger_stop_shortcut; protected: diff --git a/tests/scene/test_window.h b/tests/scene/test_window.h new file mode 100644 index 000000000000..e0c55101de3d --- /dev/null +++ b/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 diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 5ca03a20af00..f1e348345b3f 100644 --- a/tests/test_main.cpp +++ b/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"