Skip to content

Commit

Permalink
Merge pull request #84547 from kitbdev/mouse-notif-3
Browse files Browse the repository at this point in the history
Make Mouse Enter/Exit notifications match Mouse Events
  • Loading branch information
akien-mga committed Nov 9, 2023
2 parents 25e650a + d24d73b commit d36cc73
Show file tree
Hide file tree
Showing 7 changed files with 829 additions and 23 deletions.
23 changes: 18 additions & 5 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.
[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.
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions scene/gui/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
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
4 changes: 4 additions & 0 deletions scene/main/canvas_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
163 changes: 151 additions & 12 deletions scene/main/viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
5 changes: 4 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 @@ -429,6 +430,7 @@ class Viewport : public Node {

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);
Expand All @@ -455,7 +457,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 Down Expand Up @@ -494,6 +496,7 @@ class Viewport : public Node {

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; }

Expand Down
Loading

0 comments on commit d36cc73

Please sign in to comment.