Make mouse-enter/exit notifications match mouse event propagation

`NOTIFICATION_MOUSE_ENTER` and `NOTIFICATION_MOUSE_EXIT` now includes
the areas of children control nodes if the mouse filters allow it.

In order to check if a Control node itself was entered/exited, the newly
introduced `NOTIFICATION_MOUSE_ENTER_SELF` and
`NOTIFICATION_MOUSE_EXIT_SELF` can be used.

Co-authored-by: Markus Sauermann <6299227+Sauermann@users.noreply.github.com>
This commit is contained in:
kit 2023-10-31 13:55:34 -04:00
parent 4c96e9676b
commit d24d73ba31
7 changed files with 829 additions and 23 deletions

View file

@ -1104,13 +1104,13 @@
</signal>
<signal name="mouse_entered">
<description>
Emitted when the mouse cursor enters the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
Emitted when the mouse cursor enters the control's (or any child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the signal.
</description>
</signal>
<signal name="mouse_exited">
<description>
Emitted when the mouse cursor leaves the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
Emitted when the mouse cursor leaves the control's (and all child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the signal.
[b]Note:[/b] If you want to check whether the mouse truly left the area, ignoring any top nodes, you can use code like this:
[codeblock]
@ -1150,12 +1150,24 @@
Sent when the node changes size. Use [member size] to get the new size.
</constant>
<constant name="NOTIFICATION_MOUSE_ENTER" value="41">
Sent when the mouse cursor enters the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the notification.
Sent when the mouse cursor enters the control's (or any child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification.
See also [constant NOTIFICATION_MOUSE_ENTER_SELF].
</constant>
<constant name="NOTIFICATION_MOUSE_EXIT" value="42">
Sent when the mouse cursor leaves the control's (and all child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification.
See also [constant NOTIFICATION_MOUSE_EXIT_SELF].
</constant>
<constant name="NOTIFICATION_MOUSE_ENTER_SELF" value="60" is_experimental="true">
Sent when the mouse cursor enters the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification.
See also [constant NOTIFICATION_MOUSE_ENTER].
</constant>
<constant name="NOTIFICATION_MOUSE_EXIT_SELF" value="61" is_experimental="true">
Sent when the mouse cursor leaves the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the notification.
[b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification.
See also [constant NOTIFICATION_MOUSE_EXIT].
</constant>
<constant name="NOTIFICATION_FOCUS_ENTER" value="43">
Sent when the node grabs focus.
@ -1320,6 +1332,7 @@
</constant>
<constant name="MOUSE_FILTER_IGNORE" value="2" enum="MouseFilter">
The control will not receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also not receive the [signal mouse_entered] nor [signal mouse_exited] signals. This will not block other controls from receiving these events or firing the signals. Ignored events will not be handled automatically.
[b]Note:[/b] If the control has received [signal mouse_entered] but not [signal mouse_exited], changing the [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] will cause [signal mouse_exited] to be emitted.
</constant>
<constant name="GROW_DIRECTION_BEGIN" value="0" enum="GrowDirection">
The control will grow to the left or top to make up if its minimum size is changed to be greater than its current size on the respective axis.

View file

@ -1831,9 +1831,18 @@ bool Control::has_point(const Point2 &p_point) const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX(p_filter, 3);
if (data.mouse_filter == p_filter) {
return;
}
data.mouse_filter = p_filter;
notify_property_list_changed();
update_configuration_warnings();
if (get_viewport()) {
get_viewport()->_gui_update_mouse_over();
}
}
Control::MouseFilter Control::get_mouse_filter() const {
@ -3568,6 +3577,8 @@ void Control::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_RESIZED);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER_SELF);
BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT_SELF);
BIND_CONSTANT(NOTIFICATION_FOCUS_ENTER);
BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT);
BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);

View file

@ -368,6 +368,8 @@ public:
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
NOTIFICATION_MOUSE_ENTER_SELF = 60,
NOTIFICATION_MOUSE_EXIT_SELF = 61,
};
// Editor plugin interoperability.

View file

@ -462,6 +462,10 @@ void CanvasItem::set_as_top_level(bool p_top_level) {
_enter_canvas();
_notify_transform();
if (get_viewport()) {
get_viewport()->canvas_item_top_level_changed();
}
}
void CanvasItem::_top_level_changed() {

View file

@ -2408,8 +2408,8 @@ void Viewport::_gui_hide_control(Control *p_control) {
if (gui.key_focus == p_control) {
gui_release_focus();
}
if (gui.mouse_over == p_control) {
_drop_mouse_over();
if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) {
_drop_mouse_over(p_control->get_parent_control());
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
@ -2431,8 +2431,8 @@ void Viewport::_gui_remove_control(Control *p_control) {
if (gui.key_focus == p_control) {
gui.key_focus = nullptr;
}
if (gui.mouse_over == p_control) {
_drop_mouse_over();
if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) {
_drop_mouse_over(p_control->get_parent_control());
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
@ -2442,6 +2442,94 @@ void Viewport::_gui_remove_control(Control *p_control) {
}
}
void Viewport::canvas_item_top_level_changed() {
_gui_update_mouse_over();
}
void Viewport::_gui_update_mouse_over() {
if (gui.mouse_over == nullptr || gui.mouse_over_hierarchy.is_empty()) {
return;
}
// Rebuild the mouse over hierarchy.
LocalVector<Control *> new_mouse_over_hierarchy;
LocalVector<Control *> needs_enter;
LocalVector<int> needs_exit;
CanvasItem *ancestor = gui.mouse_over;
bool removing = false;
bool reached_top = false;
while (ancestor) {
Control *ancestor_control = Object::cast_to<Control>(ancestor);
if (ancestor_control) {
int found = gui.mouse_over_hierarchy.find(ancestor_control);
if (found >= 0) {
// Remove the node if the propagation chain has been broken or it is now MOUSE_FILTER_IGNORE.
if (removing || ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_IGNORE) {
needs_exit.push_back(found);
}
}
if (found == 0) {
if (removing) {
// Stop if the chain has been broken and the top of the hierarchy has been reached.
break;
}
reached_top = true;
}
if (!removing && ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
new_mouse_over_hierarchy.push_back(ancestor_control);
// Add the node if it was not found and it is now not MOUSE_FILTER_IGNORE.
if (found < 0) {
needs_enter.push_back(ancestor_control);
}
}
if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) {
// MOUSE_FILTER_STOP breaks the propagation chain.
if (reached_top) {
break;
}
removing = true;
}
}
if (ancestor->is_set_as_top_level()) {
// Top level breaks the propagation chain.
if (reached_top) {
break;
} else {
removing = true;
ancestor = Object::cast_to<CanvasItem>(ancestor->get_parent());
continue;
}
}
ancestor = ancestor->get_parent_item();
}
if (needs_exit.is_empty() && needs_enter.is_empty()) {
return;
}
// Send Mouse Exit Self notification.
if (gui.mouse_over && !needs_exit.is_empty() && needs_exit[0] == (int)gui.mouse_over_hierarchy.size() - 1) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
gui.mouse_over = nullptr;
}
// Send Mouse Exit notifications.
for (int exit_control_index : needs_exit) {
gui.mouse_over_hierarchy[exit_control_index]->notification(Control::NOTIFICATION_MOUSE_EXIT);
}
// Update the mouse over hierarchy.
gui.mouse_over_hierarchy.resize(new_mouse_over_hierarchy.size());
for (int i = 0; i < (int)new_mouse_over_hierarchy.size(); i++) {
gui.mouse_over_hierarchy[i] = new_mouse_over_hierarchy[new_mouse_over_hierarchy.size() - 1 - i];
}
// Send Mouse Enter notifications.
for (int i = needs_enter.size() - 1; i >= 0; i--) {
needs_enter[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
}
}
Window *Viewport::get_base_window() const {
ERR_READ_THREAD_GUARD_V(nullptr);
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
@ -3069,16 +3157,58 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
// Look for Controls at mouse position.
Control *over = gui_find_control(p_pos);
bool notify_embedded_viewports = false;
if (over != gui.mouse_over) {
if (gui.mouse_over) {
_drop_mouse_over();
if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) {
// Find the common ancestor of `gui.mouse_over` and `over`.
Control *common_ancestor = nullptr;
LocalVector<Control *> over_ancestors;
if (over) {
// Get all ancestors that the mouse is currently over and need an enter signal.
CanvasItem *ancestor = over;
while (ancestor) {
Control *ancestor_control = Object::cast_to<Control>(ancestor);
if (ancestor_control) {
if (ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
int found = gui.mouse_over_hierarchy.find(ancestor_control);
if (found >= 0) {
common_ancestor = gui.mouse_over_hierarchy[found];
break;
}
over_ancestors.push_back(ancestor_control);
}
if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) {
// MOUSE_FILTER_STOP breaks the propagation chain.
break;
}
}
if (ancestor->is_set_as_top_level()) {
// Top level breaks the propagation chain.
break;
}
ancestor = ancestor->get_parent_item();
}
}
if (gui.mouse_over || !gui.mouse_over_hierarchy.is_empty()) {
// Send Mouse Exit Self and Mouse Exit notifications.
_drop_mouse_over(common_ancestor);
} else {
_drop_physics_mouseover();
}
gui.mouse_over = over;
if (over) {
over->notification(Control::NOTIFICATION_MOUSE_ENTER);
gui.mouse_over = over;
gui.mouse_over_hierarchy.reserve(gui.mouse_over_hierarchy.size() + over_ancestors.size());
// Send Mouse Enter notifications to parents first.
for (int i = over_ancestors.size() - 1; i >= 0; i--) {
over_ancestors[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
gui.mouse_over_hierarchy.push_back(over_ancestors[i]);
}
// Send Mouse Enter Self notification.
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF);
notify_embedded_viewports = true;
}
}
@ -3119,7 +3249,7 @@ void Viewport::_mouse_leave_viewport() {
notification(NOTIFICATION_VP_MOUSE_EXIT);
}
void Viewport::_drop_mouse_over() {
void Viewport::_drop_mouse_over(Control *p_until_control) {
_gui_cancel_tooltip();
SubViewportContainer *c = Object::cast_to<SubViewportContainer>(gui.mouse_over);
if (c) {
@ -3131,10 +3261,19 @@ void Viewport::_drop_mouse_over() {
v->_mouse_leave_viewport();
}
}
if (gui.mouse_over->is_inside_tree()) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT);
if (gui.mouse_over && gui.mouse_over->is_inside_tree()) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
}
gui.mouse_over = nullptr;
// Send Mouse Exit notifications to children first. Don't send to p_until_control or above.
int notification_until = p_until_control ? gui.mouse_over_hierarchy.find(p_until_control) + 1 : 0;
for (int i = gui.mouse_over_hierarchy.size() - 1; i >= notification_until; i--) {
if (gui.mouse_over_hierarchy[i]->is_inside_tree()) {
gui.mouse_over_hierarchy[i]->notification(Control::NOTIFICATION_MOUSE_EXIT);
}
}
gui.mouse_over_hierarchy.resize(notification_until);
}
void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {

View file

@ -361,6 +361,7 @@ private:
BitField<MouseButtonMask> mouse_focus_mask;
Control *key_focus = nullptr;
Control *mouse_over = nullptr;
LocalVector<Control *> mouse_over_hierarchy;
Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr.
Window *windowmanager_window_over = nullptr; // Only used in root Viewport.
Control *drag_mouse_over = nullptr;
@ -429,6 +430,7 @@ private:
void _gui_remove_control(Control *p_control);
void _gui_hide_control(Control *p_control);
void _gui_update_mouse_over();
void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control);
void _gui_set_drag_preview(Control *p_base, Control *p_control);
@ -455,7 +457,7 @@ private:
void _canvas_layer_add(CanvasLayer *p_canvas_layer);
void _canvas_layer_remove(CanvasLayer *p_canvas_layer);
void _drop_mouse_over();
void _drop_mouse_over(Control *p_until_control = nullptr);
void _drop_mouse_focus();
void _drop_physics_mouseover(bool p_paused_only = false);
@ -494,6 +496,7 @@ protected:
public:
void canvas_parent_mark_dirty(Node *p_node);
void canvas_item_top_level_changed();
uint64_t get_processed_events_count() const { return event_count; }

View file

@ -50,17 +50,39 @@ protected:
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: {
if (mouse_over) {
invalid_order = true;
}
mouse_over = true;
} break;
case NOTIFICATION_MOUSE_EXIT: {
if (!mouse_over) {
invalid_order = true;
}
mouse_over = false;
} break;
case NOTIFICATION_MOUSE_ENTER_SELF: {
if (mouse_over_self) {
invalid_order = true;
}
mouse_over_self = true;
} break;
case NOTIFICATION_MOUSE_EXIT_SELF: {
if (!mouse_over_self) {
invalid_order = true;
}
mouse_over_self = false;
} break;
}
}
public:
bool mouse_over = false;
bool mouse_over_self = false;
bool invalid_order = false;
};
// `NotificationControlViewport`-derived class that additionally
@ -119,12 +141,15 @@ public:
TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
DragStart *node_a = memnew(DragStart);
Control *node_b = memnew(Control);
NotificationControlViewport *node_b = memnew(NotificationControlViewport);
Node2D *node_c = memnew(Node2D);
DragTarget *node_d = memnew(DragTarget);
Control *node_e = memnew(Control);
NotificationControlViewport *node_e = memnew(NotificationControlViewport);
Node *node_f = memnew(Node);
Control *node_g = memnew(Control);
NotificationControlViewport *node_g = memnew(NotificationControlViewport);
NotificationControlViewport *node_h = memnew(NotificationControlViewport);
NotificationControlViewport *node_i = memnew(NotificationControlViewport);
NotificationControlViewport *node_j = memnew(NotificationControlViewport);
node_a->set_name(SNAME("NodeA"));
node_b->set_name(SNAME("NodeB"));
@ -133,6 +158,9 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
node_e->set_name(SNAME("NodeE"));
node_f->set_name(SNAME("NodeF"));
node_g->set_name(SNAME("NodeG"));
node_h->set_name(SNAME("NodeH"));
node_i->set_name(SNAME("NodeI"));
node_j->set_name(SNAME("NodeJ"));
node_a->set_position(Point2i(0, 0));
node_b->set_position(Point2i(10, 10));
@ -140,16 +168,25 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
node_d->set_position(Point2i(10, 10));
node_e->set_position(Point2i(10, 100));
node_g->set_position(Point2i(10, 100));
node_h->set_position(Point2i(10, 120));
node_i->set_position(Point2i(2, 0));
node_j->set_position(Point2i(2, 0));
node_a->set_size(Point2i(30, 30));
node_b->set_size(Point2i(30, 30));
node_d->set_size(Point2i(30, 30));
node_e->set_size(Point2i(10, 10));
node_g->set_size(Point2i(10, 10));
node_h->set_size(Point2i(10, 10));
node_i->set_size(Point2i(10, 10));
node_j->set_size(Point2i(10, 10));
node_a->set_focus_mode(Control::FOCUS_CLICK);
node_b->set_focus_mode(Control::FOCUS_CLICK);
node_d->set_focus_mode(Control::FOCUS_CLICK);
node_e->set_focus_mode(Control::FOCUS_CLICK);
node_g->set_focus_mode(Control::FOCUS_CLICK);
node_h->set_focus_mode(Control::FOCUS_CLICK);
node_i->set_focus_mode(Control::FOCUS_CLICK);
node_j->set_focus_mode(Control::FOCUS_CLICK);
Window *root = SceneTree::get_singleton()->get_root();
DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton());
@ -162,6 +199,9 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// - e (Control)
// - f (Node)
// - g (Control)
// - h (Control)
// - i (Control)
// - j (Control)
root->add_child(node_a);
root->add_child(node_b);
node_b->add_child(node_c);
@ -169,12 +209,17 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
root->add_child(node_e);
node_e->add_child(node_f);
node_f->add_child(node_g);
root->add_child(node_h);
node_h->add_child(node_i);
node_i->add_child(node_j);
Point2i on_a = Point2i(5, 5);
Point2i on_b = Point2i(15, 15);
Point2i on_d = Point2i(25, 25);
Point2i on_e = Point2i(15, 105);
Point2i on_g = Point2i(15, 105);
Point2i on_i = Point2i(13, 125);
Point2i on_j = Point2i(15, 125);
Point2i on_background = Point2i(500, 500);
Point2i on_outside = Point2i(-1, -1);
@ -419,26 +464,612 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SUBCASE("[Viewport][GuiInputEvent] Mouse Motion") {
// FIXME: Tooltips are not yet tested. They likely require an internal clock.
SUBCASE("[Viewport][GuiInputEvent] Mouse Motion changes the Control, that it is over.") {
SUBCASE("[Viewport][GuiInputEvent] Mouse Motion changes the Control that it is over.") {
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_a->mouse_over);
CHECK_FALSE(node_a->mouse_over_self);
// Move over Control.
SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE);
CHECK(node_a->mouse_over);
CHECK(node_a->mouse_over_self);
// No change.
SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(1, 1), MouseButtonMask::NONE, Key::NONE);
CHECK(node_a->mouse_over);
CHECK(node_a->mouse_over_self);
// Move over other Control.
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_a->mouse_over);
CHECK_FALSE(node_a->mouse_over_self);
CHECK(node_d->mouse_over);
CHECK(node_d->mouse_over_self);
// Move to background
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_d->mouse_over);
CHECK_FALSE(node_d->mouse_over_self);
CHECK_FALSE(node_a->invalid_order);
CHECK_FALSE(node_d->invalid_order);
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation.") {
node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_g->set_mouse_filter(Control::MOUSE_FILTER_PASS);
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK_FALSE(node_d->mouse_over);
CHECK_FALSE(node_d->mouse_over_self);
// Move to Control node_d. node_b receives mouse over since it is only separated by a CanvasItem.
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
CHECK(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK(node_d->mouse_over);
CHECK(node_d->mouse_over_self);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK_FALSE(node_d->mouse_over);
CHECK_FALSE(node_d->mouse_over_self);
CHECK_FALSE(node_e->mouse_over);
CHECK_FALSE(node_e->mouse_over_self);
CHECK_FALSE(node_g->mouse_over);
CHECK_FALSE(node_g->mouse_over_self);
// Move to Control node_g. node_g receives mouse over but node_e does not since it is separated by a non-CanvasItem.
SEND_GUI_MOUSE_MOTION_EVENT(on_g, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_e->mouse_over);
CHECK_FALSE(node_e->mouse_over_self);
CHECK(node_g->mouse_over);
CHECK(node_g->mouse_over_self);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_e->mouse_over);
CHECK_FALSE(node_e->mouse_over_self);
CHECK_FALSE(node_g->mouse_over);
CHECK_FALSE(node_g->mouse_over_self);
CHECK_FALSE(node_b->invalid_order);
CHECK_FALSE(node_d->invalid_order);
CHECK_FALSE(node_e->invalid_order);
CHECK_FALSE(node_g->invalid_order);
node_d->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_g->set_mouse_filter(Control::MOUSE_FILTER_STOP);
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation when moving into child.") {
SIGNAL_WATCH(node_i, SNAME("mouse_entered"));
SIGNAL_WATCH(node_i, SNAME("mouse_exited"));
Array signal_args;
signal_args.push_back(Array());
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
// Move to Control node_i.
SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE);
CHECK(node_i->mouse_over);
CHECK(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Move to child Control node_j. node_i should not receive any new Mouse Enter signals.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Move to parent Control node_i. node_i should not receive any new Mouse Enter signals.
SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE);
CHECK(node_i->mouse_over);
CHECK(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK(SNAME("mouse_exited"), signal_args);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
SIGNAL_UNWATCH(node_i, SNAME("mouse_entered"));
SIGNAL_UNWATCH(node_i, SNAME("mouse_exited"));
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with top level.") {
node_c->set_as_top_level(true);
node_i->set_as_top_level(true);
node_c->set_position(node_b->get_global_position());
node_i->set_position(node_h->get_global_position());
node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK_FALSE(node_d->mouse_over);
CHECK_FALSE(node_d->mouse_over_self);
// Move to Control node_d. node_b does not receive mouse over since node_c is top level.
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK(node_d->mouse_over);
CHECK(node_d->mouse_over_self);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK_FALSE(node_d->mouse_over);
CHECK_FALSE(node_d->mouse_over_self);
CHECK_FALSE(node_g->mouse_over);
CHECK_FALSE(node_g->mouse_over_self);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
// Move to Control node_j. node_h does not receive mouse over since node_i is top level.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
CHECK_FALSE(node_b->invalid_order);
CHECK_FALSE(node_d->invalid_order);
CHECK_FALSE(node_e->invalid_order);
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_c->set_as_top_level(false);
node_i->set_as_top_level(false);
node_c->set_position(Point2i(0, 0));
node_i->set_position(Point2i(0, 0));
node_d->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with mouse filter stop.") {
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
// Move to Control node_j. node_h does not receive mouse over since node_i is MOUSE_FILTER_STOP.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with mouse filter ignore.") {
node_i->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
// Move to Control node_j. node_i does not receive mouse over since node_i is MOUSE_FILTER_IGNORE.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing top level.") {
SIGNAL_WATCH(node_i, SNAME("mouse_entered"));
SIGNAL_WATCH(node_i, SNAME("mouse_exited"));
Array signal_args;
signal_args.push_back(Array());
node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to Control node_d.
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
CHECK(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK(node_d->mouse_over);
CHECK(node_d->mouse_over_self);
// Change node_c to be top level. node_b should receive Mouse Exit.
node_c->set_as_top_level(true);
CHECK_FALSE(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK(node_d->mouse_over);
CHECK(node_d->mouse_over_self);
// Change node_c to be not top level. node_b should receive Mouse Enter.
node_c->set_as_top_level(false);
CHECK(node_b->mouse_over);
CHECK_FALSE(node_b->mouse_over_self);
CHECK(node_d->mouse_over);
CHECK(node_d->mouse_over_self);
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_i to top level. node_h should receive Mouse Exit. node_i should not receive any new signals.
node_i->set_as_top_level(true);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_i to not top level. node_h should receive Mouse Enter. node_i should not receive any new signals.
node_i->set_as_top_level(false);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
CHECK_FALSE(node_b->invalid_order);
CHECK_FALSE(node_d->invalid_order);
CHECK_FALSE(node_e->invalid_order);
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_d->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
SIGNAL_UNWATCH(node_i, SNAME("mouse_entered"));
SIGNAL_UNWATCH(node_i, SNAME("mouse_exited"));
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to stop.") {
SIGNAL_WATCH(node_i, SNAME("mouse_entered"));
SIGNAL_WATCH(node_i, SNAME("mouse_exited"));
Array signal_args;
signal_args.push_back(Array());
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_i to MOUSE_FILTER_STOP. node_h should receive Mouse Exit. node_i should not receive any new signals.
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
CHECK_FALSE(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_i to MOUSE_FILTER_PASS. node_h should receive Mouse Enter. node_i should not receive any new signals.
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
SIGNAL_UNWATCH(node_i, SNAME("mouse_entered"));
SIGNAL_UNWATCH(node_i, SNAME("mouse_exited"));
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to ignore.") {
SIGNAL_WATCH(node_i, SNAME("mouse_entered"));
SIGNAL_WATCH(node_i, SNAME("mouse_exited"));
Array signal_args;
signal_args.push_back(Array());
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_i to MOUSE_FILTER_IGNORE. node_i should receive Mouse Exit.
node_i->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK(SNAME("mouse_exited"), signal_args);
// Change node_i to MOUSE_FILTER_PASS. node_i should receive Mouse Enter.
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_j to MOUSE_FILTER_IGNORE. After updating the mouse motion, node_i should now have mouse_over_self.
node_j->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Change node_j to MOUSE_FILTER_PASS. After updating the mouse motion, node_j should now have mouse_over_self.
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
SIGNAL_UNWATCH(node_i, SNAME("mouse_entered"));
SIGNAL_UNWATCH(node_i, SNAME("mouse_exited"));
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when removing the hovered Control.") {
SIGNAL_WATCH(node_h, SNAME("mouse_entered"));
SIGNAL_WATCH(node_h, SNAME("mouse_exited"));
Array signal_args;
signal_args.push_back(Array());
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Remove node_i from the tree. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals.
node_h->remove_child(node_i);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Add node_i to the tree and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals.
node_h->add_child(node_i);
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
SIGNAL_UNWATCH(node_h, SNAME("mouse_entered"));
SIGNAL_UNWATCH(node_h, SNAME("mouse_exited"));
}
SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when hiding the hovered Control.") {
SIGNAL_WATCH(node_h, SNAME("mouse_entered"));
SIGNAL_WATCH(node_h, SNAME("mouse_exited"));
Array signal_args;
signal_args.push_back(Array());
node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS);
node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS);
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Hide node_i. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals.
node_i->hide();
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK_FALSE(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK_FALSE(node_j->mouse_over);
CHECK_FALSE(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
// Show node_i and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals.
node_i->show();
SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE);
CHECK(node_h->mouse_over);
CHECK_FALSE(node_h->mouse_over_self);
CHECK(node_i->mouse_over);
CHECK_FALSE(node_i->mouse_over_self);
CHECK(node_j->mouse_over);
CHECK(node_j->mouse_over_self);
SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
CHECK_FALSE(node_h->invalid_order);
CHECK_FALSE(node_i->invalid_order);
CHECK_FALSE(node_j->invalid_order);
node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP);
node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP);
SIGNAL_UNWATCH(node_h, SNAME("mouse_entered"));
SIGNAL_UNWATCH(node_h, SNAME("mouse_exited"));
}
SUBCASE("[Viewport][GuiInputEvent] Window Mouse Enter/Exit signals.") {
@ -710,6 +1341,9 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
}
}
memdelete(node_j);
memdelete(node_i);
memdelete(node_h);
memdelete(node_g);
memdelete(node_f);
memdelete(node_e);