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:
parent
4c96e9676b
commit
d24d73ba31
7 changed files with 829 additions and 23 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue