Skip to content

Commit

Permalink
Consider children Control nodes for mouse-enter/exit notifications
Browse files Browse the repository at this point in the history
`NOTIFICATION_MOUSE_ENTER` and `NOTIFICATION_MOUSE_EXIT` now includes
the areas of children control nodes.

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 <[email protected]>
  • Loading branch information
kitbdev and Sauermann committed Nov 1, 2023
1 parent 4363ae3 commit 1eda4ce
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 16 deletions.
18 changes: 15 additions & 3 deletions doc/classes/Control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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.
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">
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">
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.
See also [constant NOTIFICATION_MOUSE_EXIT].
</constant>
<constant name="NOTIFICATION_FOCUS_ENTER" value="43">
Sent when the node grabs focus.
Expand Down
2 changes: 2 additions & 0 deletions scene/gui/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3568,6 +3568,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);
Expand Down
2 changes: 2 additions & 0 deletions scene/gui/control.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ class Control : public CanvasItem {
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.
Expand Down
80 changes: 68 additions & 12 deletions scene/main/viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2408,9 +2408,7 @@ 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();
}
_reevaluate_mouse_over();
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
}
Expand All @@ -2431,8 +2429,9 @@ 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());
callable_mp(this, &Viewport::_reevaluate_mouse_over).call_deferred();
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
Expand Down Expand Up @@ -2987,6 +2986,14 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
return true;
}

void Viewport::_reevaluate_mouse_over() {
if (!gui.mouse_in_viewport) {
return;
}
// Send mouse entered exited notifications for current viewport.
_update_mouse_over(gui.last_mouse_pos);
}

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 MOUSE_ENTER/MOUSE_EXIT notifications for every Viewport in the SceneTree.
Expand Down Expand Up @@ -3070,15 +3077,55 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
Control *over = gui_find_control(p_pos);
bool notify_embedded_viewports = false;
if (over != gui.mouse_over) {
if (gui.mouse_over) {
_drop_mouse_over();
// 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.
Control *ancestor = over;
while (ancestor) {
int found = gui.mouse_over_hierarchy.find(ancestor);
if (found >= 0) {
common_ancestor = gui.mouse_over_hierarchy[found];
break;
}
if (ancestor->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
over_ancestors.push_back(ancestor);
}
if (ancestor->is_set_as_top_level() || ancestor->get_mouse_filter() == Control::MOUSE_FILTER_STOP) {
// Top level Control nodes and MOUSE_FILTER_STOP break the propagation chain.
break;
}
// Consider only direct Control parents. (Could be changed to include non-Control CanvasItems.)
ancestor = ancestor->get_parent_control();
}
}

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.
if (!over_ancestors.is_empty()) {
over_ancestors[0]->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF);
} else if (gui.mouse_over->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF);
}

notify_embedded_viewports = true;
}
}
Expand Down Expand Up @@ -3119,7 +3166,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) {
Expand All @@ -3131,10 +3178,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) {
Expand Down
4 changes: 3 additions & 1 deletion scene/main/viewport.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ class Viewport : public Node {
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;
Expand Down Expand Up @@ -455,7 +456,7 @@ class Viewport : public Node {
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);

Expand All @@ -475,6 +476,7 @@ class Viewport : public Node {
void _update_mouse_over();
virtual void _update_mouse_over(Vector2 p_pos);
virtual void _mouse_leave_viewport();
void _reevaluate_mouse_over();

virtual bool _can_consume_input_events() const { return true; }
uint64_t event_count = 0;
Expand Down

0 comments on commit 1eda4ce

Please sign in to comment.