diff options
Diffstat (limited to 'scene')
-rw-r--r-- | scene/gui/control.cpp | 11 | ||||
-rw-r--r-- | scene/gui/control.h | 2 | ||||
-rw-r--r-- | scene/main/canvas_item.cpp | 4 | ||||
-rw-r--r-- | scene/main/viewport.cpp | 163 | ||||
-rw-r--r-- | scene/main/viewport.h | 5 |
5 files changed, 172 insertions, 13 deletions
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index c7ff5980cb..87539ef8f2 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -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); diff --git a/scene/gui/control.h b/scene/gui/control.h index abbdc42fa4..db1bd3a346 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -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. diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index a350b97bc8..4ee81e5cb0 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -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() { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 2b28f21f57..43bdb1395b 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -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) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 65777c973f..82a9bfc438 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -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; } |