You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-20 14:45:44 +00:00
Merge pull request #110256 from Koyper/fix_popup_menu_item_wont_open
[PopupMenu] Fix submenu item not popping on mouse enter
This commit is contained in:
@@ -346,68 +346,67 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
|
||||
}
|
||||
|
||||
void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
|
||||
ERR_FAIL_INDEX_MSG(p_over, items.size(), vformat("Invalid submenu index %d in _activate_submenu.", p_over));
|
||||
PopupMenu *submenu_popup = items[p_over].submenu;
|
||||
if (submenu_popup->is_visible()) {
|
||||
return; // Already visible.
|
||||
}
|
||||
ERR_FAIL_COND_MSG(submenu_popup->is_visible(), vformat("_activate_submenu should not be called on an open submenu - index: %d.", p_over));
|
||||
|
||||
submenu_popup->this_submenu_index = p_over;
|
||||
active_submenu_index = p_over;
|
||||
|
||||
submenu_popup->get_window()->set_exclusive(false); // Ensure mouse inputs to parent menu are not inhibited by the submenu in exclusive mode.
|
||||
|
||||
const float win_scale = get_content_scale_factor();
|
||||
|
||||
const Point2 panel_ofs_start = Point2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * win_scale;
|
||||
const Point2 panel_ofs_end = Point2(-panel->get_offset(SIDE_RIGHT), -panel->get_offset(SIDE_BOTTOM)) * win_scale;
|
||||
|
||||
const Point2 this_pos = get_position() + Point2(0, panel_ofs_start.y + theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale);
|
||||
Rect2 this_rect(this_pos, get_size());
|
||||
|
||||
const float scroll_offset = control->get_position().y;
|
||||
const float scaled_ofs_cache = items[p_over]._ofs_cache * win_scale;
|
||||
const float scaled_height_cache = items[p_over]._height_cache * win_scale;
|
||||
const Point2 this_pos = get_position();
|
||||
Rect2 this_rect = Rect2(this_pos, panel->get_size());
|
||||
|
||||
submenu_popup->reset_size(); // Shrink the popup size to its contents.
|
||||
const Size2 submenu_size = submenu_popup->get_size();
|
||||
|
||||
// Calculate the submenu's position.
|
||||
Point2 submenu_pos(0, -submenu_popup->get_theme_stylebox(SceneStringName(panel))->get_margin(SIDE_TOP) * submenu_popup->get_content_scale_factor());
|
||||
Point2 submenu_pos = Point2(0, 0);
|
||||
Rect2i screen_rect = is_embedded() ? Rect2i(get_embedder()->get_visible_rect()) : get_parent_rect();
|
||||
active_submenu_target_line.clear();
|
||||
|
||||
panel_offset_start = Point2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * win_scale;
|
||||
const Point2 panel_offset_end = Point2(-panel->get_offset(SIDE_RIGHT), -panel->get_offset(SIDE_BOTTOM)) * win_scale;
|
||||
const Vector2 scaled_this_size = this_rect.size * win_scale;
|
||||
const float scaled_theme_v_separation = theme_cache.v_separation * win_scale;
|
||||
const float scroll_offset = control->get_position().y;
|
||||
const float scaled_ofs_cache = items[p_over]._ofs_cache * win_scale;
|
||||
const float scaled_height_cache = items[p_over]._height_cache * win_scale;
|
||||
|
||||
if (is_layout_rtl()) {
|
||||
submenu_pos += this_pos + Point2(-submenu_size.width + panel_ofs_end.x, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2);
|
||||
is_active_submenu_left = true;
|
||||
submenu_pos += this_pos + Point2(-submenu_size.width + panel_offset_end.x, scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + panel_offset_start.y);
|
||||
if (submenu_pos.x < screen_rect.position.x) {
|
||||
submenu_pos.x = this_pos.x + this_rect.size.width - panel_ofs_start.x;
|
||||
submenu_pos.x = this_pos.x + this_rect.size.width - panel_offset_start.x;
|
||||
is_active_submenu_left = false;
|
||||
}
|
||||
|
||||
this_rect.position.x += panel_ofs_end.x;
|
||||
} else {
|
||||
submenu_pos += this_pos + Point2(this_rect.size.width - panel_ofs_end.x, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2);
|
||||
is_active_submenu_left = false;
|
||||
submenu_pos += this_pos + Point2(scaled_this_size.x + panel_offset_start.x, scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + panel_offset_start.y);
|
||||
if (submenu_pos.x + submenu_size.width > screen_rect.position.x + screen_rect.size.width) {
|
||||
submenu_pos.x = this_pos.x - submenu_size.width + panel_ofs_start.x;
|
||||
submenu_pos.x = this_pos.x - submenu_size.width + panel_offset_end.x;
|
||||
is_active_submenu_left = true;
|
||||
}
|
||||
|
||||
this_rect.position.x += panel_ofs_start.x;
|
||||
}
|
||||
|
||||
submenu_popup->set_position(submenu_pos);
|
||||
|
||||
PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup);
|
||||
if (!submenu_pum) {
|
||||
submenu_popup->popup();
|
||||
return;
|
||||
}
|
||||
|
||||
submenu_pum->activated_by_keyboard = p_by_keyboard;
|
||||
|
||||
submenu_popup->activated_by_keyboard = p_by_keyboard;
|
||||
// If not triggered by the mouse, start the popup with its first enabled item focused.
|
||||
if (p_by_keyboard) {
|
||||
for (int i = 0; i < submenu_pum->get_item_count(); i++) {
|
||||
if (!submenu_pum->is_item_disabled(i)) {
|
||||
submenu_pum->set_focused_item(i);
|
||||
for (int i = 0; i < submenu_popup->get_item_count(); i++) {
|
||||
if (!submenu_popup->is_item_disabled(i)) {
|
||||
submenu_popup->set_focused_item(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submenu_pum->popup();
|
||||
|
||||
// Set autohide areas.
|
||||
submenu_popup->popup();
|
||||
// The autohide areas are set on the submenu, but are aligned over the parent menu,
|
||||
// so we spoof `this_rect` position as the negative relative offset of the parent from the submenu.
|
||||
this_rect.position = -(submenu_popup->get_position() - this_pos);
|
||||
|
||||
const Rect2 safe_area(get_position(), get_size());
|
||||
Viewport *vp = submenu_popup->get_embedder();
|
||||
@@ -416,21 +415,29 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
|
||||
} else {
|
||||
DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area);
|
||||
}
|
||||
|
||||
this_rect.position -= submenu_pum->get_position(); // Make the position of the parent popup relative to submenu popup.
|
||||
this_rect.size.width -= panel_ofs_start.x + panel_ofs_end.x;
|
||||
this_rect.size.height -= panel_ofs_end.y + (theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM)) * win_scale;
|
||||
|
||||
// Autohide area above the submenu item.
|
||||
submenu_pum->clear_autohide_areas();
|
||||
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y - theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale,
|
||||
this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale - theme_cache.v_separation / 2));
|
||||
|
||||
// If there is an area below the submenu item, add an autohide area there.
|
||||
if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height * win_scale) {
|
||||
const int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2;
|
||||
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
|
||||
// Set the mouse movement target line at the top and bottom points of the submenu vertical side abutting the parent menu.
|
||||
if (is_active_submenu_left) {
|
||||
active_submenu_target_line.push_back(Point2(submenu_popup->get_position().x + submenu_popup->get_size().x, submenu_popup->get_position().y));
|
||||
} else {
|
||||
active_submenu_target_line.push_back(submenu_popup->get_position());
|
||||
}
|
||||
active_submenu_target_line.push_back(Point2(active_submenu_target_line[0].x, active_submenu_target_line[0].y + submenu_popup->get_size().y));
|
||||
|
||||
submenu_popup->clear_autohide_areas();
|
||||
// Add an autohide area above the submenu item unless it's the top item.
|
||||
// This avoids a narrow strip of area that can trigger the submenu to reload when reentering the parent item from the top.
|
||||
const int y_to_item_top = scaled_ofs_cache + scroll_offset - int(scaled_theme_v_separation * 0.5) + theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale;
|
||||
Rect2 top_rect = Rect2(this_rect.position.x, this_rect.position.y, scaled_this_size.width, y_to_item_top);
|
||||
if (active_submenu_index != 0) {
|
||||
submenu_popup->add_autohide_area(top_rect);
|
||||
}
|
||||
// If there is an area below the submenu item, add an autohide area there unless it's the last item.
|
||||
if (active_submenu_index != items.size() - 1) {
|
||||
const int y_to_item_bottom = y_to_item_top + scaled_height_cache + scaled_theme_v_separation;
|
||||
submenu_popup->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + y_to_item_bottom, scaled_this_size.x, scaled_this_size.y - y_to_item_bottom));
|
||||
}
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
}
|
||||
|
||||
void PopupMenu::_parent_focused() {
|
||||
@@ -460,8 +467,6 @@ void PopupMenu::_submenu_timeout() {
|
||||
if (mouse_over == submenu_over) {
|
||||
_activate_submenu(mouse_over);
|
||||
}
|
||||
|
||||
submenu_over = -1;
|
||||
}
|
||||
|
||||
void PopupMenu::_input_from_window(const Ref<InputEvent> &p_event) {
|
||||
@@ -572,7 +577,8 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
} else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
|
||||
Node *n = get_parent();
|
||||
if (n) {
|
||||
if (Object::cast_to<PopupMenu>(n)) {
|
||||
if (PopupMenu *parent_popup = Object::cast_to<PopupMenu>(n)) {
|
||||
parent_popup->activated_by_keyboard = true;
|
||||
hide();
|
||||
set_input_as_handled();
|
||||
} else if (Object::cast_to<MenuBar>(n)) {
|
||||
@@ -650,8 +656,12 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
if (!item_clickable_area.has_point(b->get_position())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_mouse_over_update(b->get_position());
|
||||
int over = _get_mouse_over(b->get_position());
|
||||
if (over < 0 || items[over].separator || items[over].disabled || (items[over].submenu && items[over].submenu->is_visible())) {
|
||||
return;
|
||||
} else {
|
||||
_mouse_over_update(b->get_position());
|
||||
}
|
||||
} else {
|
||||
if (is_scrolling) {
|
||||
is_scrolling = false;
|
||||
@@ -683,7 +693,9 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
|
||||
if (items[over].submenu) {
|
||||
_activate_submenu(over);
|
||||
if (!items[over].submenu->is_visible()) {
|
||||
_activate_submenu(over);
|
||||
}
|
||||
return;
|
||||
}
|
||||
activate_item(over);
|
||||
@@ -694,28 +706,40 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
Ref<InputEventMouseMotion> m = p_event;
|
||||
|
||||
if (m.is_valid()) {
|
||||
if (m->get_velocity().is_zero_approx()) {
|
||||
if (m->get_velocity().is_zero_approx() || m->get_relative() == Vector2(0, 0)) {
|
||||
return;
|
||||
}
|
||||
activated_by_keyboard = false;
|
||||
|
||||
for (const Rect2 &E : autohide_areas) {
|
||||
if (!scroll_container->get_global_rect().has_point(m->get_position()) && E.has_point(m->get_position())) {
|
||||
// The mouse left the safe area, prepare to close.
|
||||
_close_pressed();
|
||||
return;
|
||||
if (this_submenu_index != -1) { // Is a submenu.
|
||||
PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent());
|
||||
Point2 areas_mouse_pos = get_mouse_position() - parent_popup->panel_offset_start;
|
||||
for (const Rect2 &E : autohide_areas) {
|
||||
if (!scroll_container->get_global_rect().has_point(m->get_position()) && E.has_point(areas_mouse_pos)) {
|
||||
_close_or_suspend();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!minimum_lifetime_timer->is_stopped()) {
|
||||
if (this_submenu_index != -1) { // Is a submenu.
|
||||
// The mouse left the safe area, but came back again, so cancel the auto-closing.
|
||||
minimum_lifetime_timer->stop();
|
||||
if (PopupMenu *parent_pum = Object::cast_to<PopupMenu>(get_parent())) {
|
||||
parent_pum->_hover_active_submenu_item();
|
||||
if (PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent())) {
|
||||
if (!parent_popup->close_suspended_timer->is_stopped()) {
|
||||
parent_popup->close_suspended_timer->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_over == -1 && !item_clickable_area.has_point(m->get_position())) {
|
||||
Vector2 m_relative = m->get_relative();
|
||||
if (is_embedded() && m_relative != Vector2()) { // Only if the mouse has moved relative.
|
||||
if (PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent())) { // Is a submenu.
|
||||
parent_popup->last_submenu_mouse_position = m->get_position();
|
||||
} else {
|
||||
last_submenu_mouse_position = m->get_position();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
_mouse_over_update(m->get_position());
|
||||
@@ -764,27 +788,50 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_mouse_over_update(const Point2 &p_over) {
|
||||
int over = _get_mouse_over(p_over);
|
||||
int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over);
|
||||
bool PopupMenu::_is_mouse_moving_toward_submenu(const Vector2 &p_relative, bool p_is_submenu_left, const Vector2 &p_mouse_position, const Vector<Point2> &p_active_submenu_target_line) const {
|
||||
Vector2 top_target = (p_active_submenu_target_line[0] - p_mouse_position).rotated(p_is_submenu_left ? -Math::PI * 0.5 : Math::PI * 0.5);
|
||||
Vector2 bottom_target = (p_active_submenu_target_line[1] - p_mouse_position).rotated(p_is_submenu_left ? Math::PI * 0.5 : -Math::PI * 0.5);
|
||||
// The top_target vector is perpendicular to the vector between the mouse position and the top point of the submenu target line.
|
||||
// The dot product is > 0 if the relative vector is +/- 90 degrees of the top_vector.
|
||||
// The bottom_target vector is perpendicular to the vector between the mouse position and the bottom point of the submenu target line,
|
||||
// but pointing in the opposite direction of the top_target vector. Thus, the intersection of top and bottom test semicircles is the area
|
||||
// in which the relative vector is deemed to be moving toward the submenu.
|
||||
// Without normalization, the comparison is correct by testing only the sign, but would not work against a range of +/- 1.
|
||||
return bottom_target.dot(p_relative) > 0 && top_target.dot(p_relative) > 0;
|
||||
}
|
||||
|
||||
void PopupMenu::_mouse_over_update(const Point2 &p_over) {
|
||||
int over_index = _get_mouse_over(p_over);
|
||||
int id = (over_index < 0 || items[over_index].separator || items[over_index].disabled) ? -1 : (items[over_index].id >= 0 ? items[over_index].id : over_index);
|
||||
if (id < 0) {
|
||||
// Only remove the hover if there's no open submenu, or the mouse is in an item that can't be hovered.
|
||||
if (over >= 0 || !(mouse_over >= 0 && items[mouse_over].submenu && items[mouse_over].submenu->is_visible())) {
|
||||
mouse_over = -1;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
}
|
||||
mouse_over = -1;
|
||||
submenu_over = -1;
|
||||
submenu_timer->stop();
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_scrolling && items[over].submenu && submenu_over != over) {
|
||||
submenu_over = over;
|
||||
submenu_timer->start();
|
||||
if (items[over_index].submenu) {
|
||||
if (items[over_index].submenu->is_visible()) {
|
||||
if (submenu_over == over_index) {
|
||||
last_submenu_mouse_position = is_embedded() ? p_over : Point2(DisplayServer::get_singleton()->mouse_get_position() - get_position());
|
||||
}
|
||||
if (items[over_index].submenu->active_submenu_index != -1) { // Close any secondary popup open on the visible submenu when the mouse is on the parent item.
|
||||
PopupMenu *secondary_popup = Object::cast_to<PopupMenu>(items[over_index].submenu);
|
||||
ERR_FAIL_INDEX_MSG(secondary_popup->active_submenu_index, secondary_popup->items.size(), vformat("Invalid active_submenu_index index %d in _mouse_over_update.", secondary_popup->active_submenu_index));
|
||||
secondary_popup->items[secondary_popup->active_submenu_index].submenu->_close_pressed();
|
||||
}
|
||||
} else if (!is_scrolling && submenu_over != over_index && !close_was_suspended && active_submenu_index == -1) {
|
||||
submenu_timer->start();
|
||||
submenu_over = over_index;
|
||||
}
|
||||
} else {
|
||||
submenu_timer->stop();
|
||||
submenu_over = -1;
|
||||
}
|
||||
|
||||
if (over != mouse_over) {
|
||||
mouse_over = over;
|
||||
if (over_index != mouse_over) {
|
||||
mouse_over = over_index;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
}
|
||||
@@ -846,8 +893,7 @@ void PopupMenu::_draw_items() {
|
||||
Point2 item_ofs = ofs;
|
||||
Size2 icon_size = _get_item_icon_size(i);
|
||||
float h = _get_item_height(i);
|
||||
|
||||
if (i == mouse_over) {
|
||||
if ((active_submenu_index == -1 && i == mouse_over) || i == active_submenu_index) {
|
||||
theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(0, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation)));
|
||||
}
|
||||
|
||||
@@ -963,13 +1009,13 @@ void PopupMenu::_draw_items() {
|
||||
if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
|
||||
items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
|
||||
}
|
||||
items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color));
|
||||
items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : ((active_submenu_index == -1 && i == mouse_over) || i == active_submenu_index ? theme_cache.font_hover_color : theme_cache.font_color));
|
||||
} else {
|
||||
Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
|
||||
if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
|
||||
items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
|
||||
}
|
||||
items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color));
|
||||
items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : ((active_submenu_index == -1 && i == mouse_over) || i == active_submenu_index ? theme_cache.font_hover_color : theme_cache.font_color));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -995,28 +1041,52 @@ void PopupMenu::_draw_items() {
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_minimum_lifetime_timeout() {
|
||||
close_allowed = true;
|
||||
// If the mouse still isn't in this popup after timer expires, close.
|
||||
if (!activated_by_keyboard && !get_visible_rect().has_point(get_mouse_position())) {
|
||||
_close_pressed();
|
||||
void PopupMenu::_close_pressed() {
|
||||
if (this_submenu_index != -1 && active_submenu_index != -1) { // Close secondary submenus on first submenu close.
|
||||
ERR_FAIL_INDEX_MSG(active_submenu_index, items.size(), vformat("Invalid active_submenu_index index %d in _close_pressed.", active_submenu_index));
|
||||
items[active_submenu_index].submenu->_close_pressed();
|
||||
}
|
||||
Popup::_close_pressed();
|
||||
}
|
||||
|
||||
void PopupMenu::_close_pressed() {
|
||||
// Only apply minimum lifetime to submenus.
|
||||
PopupMenu *parent_pum = Object::cast_to<PopupMenu>(get_parent());
|
||||
if (!parent_pum) {
|
||||
Popup::_close_pressed();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the timer has expired, close. If timer is still running, do nothing.
|
||||
if (close_allowed) {
|
||||
close_allowed = false;
|
||||
Popup::_close_pressed();
|
||||
} else if (minimum_lifetime_timer->is_stopped()) {
|
||||
minimum_lifetime_timer->start();
|
||||
void PopupMenu::_close_or_suspend() {
|
||||
if (this_submenu_index != -1) { // Is a submenu.
|
||||
PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent());
|
||||
Point2 mouse_pos = is_embedded() ? parent_popup->get_mouse_position() : Point2(DisplayServer::get_singleton()->mouse_get_position() - parent_popup->get_position());
|
||||
if (parent_popup->_get_mouse_over(mouse_pos) == this_submenu_index) {
|
||||
parent_popup->submenu_mouse_exited_ticks_msec = -1;
|
||||
parent_popup->mouse_movement_was_tested = false;
|
||||
parent_popup->close_suspended_timer->stop();
|
||||
parent_popup->close_was_suspended = false;
|
||||
parent_popup->submenu_timer->stop();
|
||||
return;
|
||||
}
|
||||
if (parent_popup->submenu_mouse_exited_ticks_msec == -1) { // ticks are reset to -1 when any open submenu is hidden.
|
||||
parent_popup->submenu_mouse_exited_ticks_msec = OS::get_singleton()->get_ticks_msec();
|
||||
}
|
||||
if (!parent_popup->close_was_suspended && parent_popup->close_suspended_timer->is_stopped()) { // Provisionally suspend submenu close until the check below after several msecs.
|
||||
parent_popup->close_suspended_timer->start();
|
||||
parent_popup->close_was_suspended = true; // Stays true until the submenu is closed so it can only be suspended once.
|
||||
parent_popup->submenu_timer->stop();
|
||||
} else if (!parent_popup->mouse_movement_was_tested && OS::get_singleton()->get_ticks_msec() - parent_popup->submenu_mouse_exited_ticks_msec > 30) {
|
||||
// Allow the mouse to move away for several msecs before calculating the relative vector to the mouse exit point out of the submenu item.
|
||||
parent_popup->mouse_movement_was_tested = true;
|
||||
if (is_embedded()) { // Correct for relative positioning of embedded subwindows.
|
||||
parent_popup->last_submenu_mouse_position += get_position() - parent_popup->get_position();
|
||||
}
|
||||
const Vector2 screen_mouse_position = mouse_pos + parent_popup->get_position();
|
||||
Vector2 mouse_relative_to_submenu_exit = mouse_pos - parent_popup->last_submenu_mouse_position;
|
||||
// If the mouse is not moving toward the submenu target line, force the submenu closed.
|
||||
if (!_is_mouse_moving_toward_submenu(mouse_relative_to_submenu_exit, parent_popup->is_active_submenu_left, screen_mouse_position, parent_popup->active_submenu_target_line)) {
|
||||
parent_popup->close_suspended_timer->stop();
|
||||
parent_popup->close_was_suspended = false;
|
||||
_close_pressed();
|
||||
}
|
||||
} else if (parent_popup->mouse_movement_was_tested && OS::get_singleton()->get_ticks_msec() - parent_popup->submenu_mouse_exited_ticks_msec > CLOSE_SUSPENDED_TIMER_DELAY * 1000) {
|
||||
_close_pressed(); // This is called when the mouse is moved off an open submenu onto an autohide area.
|
||||
}
|
||||
} else { // Is the parent popup menu.
|
||||
_close_pressed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1097,19 +1167,6 @@ Rect2i PopupMenu::_popup_adjust_rect() const {
|
||||
return current;
|
||||
}
|
||||
|
||||
void PopupMenu::_hover_active_submenu_item() {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].submenu && items[i].submenu->is_visible()) {
|
||||
if (mouse_over != i) {
|
||||
mouse_over = i;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::add_child_notify(Node *p_child) {
|
||||
Window::add_child_notify(p_child);
|
||||
|
||||
@@ -1299,11 +1356,9 @@ void PopupMenu::_notification(int p_what) {
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_WM_MOUSE_EXIT: {
|
||||
if (mouse_over >= 0 && (!items[mouse_over].submenu || !items[mouse_over].submenu->is_visible())) {
|
||||
mouse_over = -1;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
}
|
||||
mouse_over = -1;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_WM_SIZE_CHANGED: {
|
||||
@@ -1403,14 +1458,15 @@ void PopupMenu::_notification(int p_what) {
|
||||
}
|
||||
}
|
||||
|
||||
// Only used when using operating system windows.
|
||||
if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) {
|
||||
Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
|
||||
mouse_pos -= get_position();
|
||||
|
||||
// Only used when using operating system windows, and only on submenus.
|
||||
if (!activated_by_keyboard && !is_embedded() && autohide_areas.size() && this_submenu_index != -1) {
|
||||
PopupMenu *parent_popup = Object::cast_to<PopupMenu>(get_parent());
|
||||
const float win_scale = get_content_scale_factor();
|
||||
Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - get_position();
|
||||
Point2 areas_mouse_pos = mouse_pos - parent_popup->panel_offset_start;
|
||||
for (const Rect2 &E : autohide_areas) {
|
||||
if (!Rect2(Point2(), get_size()).has_point(mouse_pos) && E.has_point(mouse_pos)) {
|
||||
_close_pressed();
|
||||
if (!Rect2(Point2(), control->get_size() * win_scale).has_point(areas_mouse_pos) && E.has_point(areas_mouse_pos)) {
|
||||
_close_or_suspend();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1869,6 +1925,7 @@ void PopupMenu::add_submenu_node_item(const String &p_label, PopupMenu *p_submen
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
|
||||
p_submenu->connect("popup_hide", callable_mp(this, &PopupMenu::_submenu_hidden));
|
||||
child_controls_changed();
|
||||
notify_property_list_changed();
|
||||
_menu_changed();
|
||||
@@ -2159,12 +2216,43 @@ void PopupMenu::set_item_submenu_node(int p_idx, PopupMenu *p_submenu) {
|
||||
items.write[p_idx].submenu_bound = true;
|
||||
}
|
||||
}
|
||||
|
||||
p_submenu->connect("popup_hide", callable_mp(this, &PopupMenu::_submenu_hidden));
|
||||
control->queue_redraw();
|
||||
child_controls_changed();
|
||||
_menu_changed();
|
||||
}
|
||||
|
||||
void PopupMenu::_close_suspended_timeout() {
|
||||
if (submenu_over != -1 && is_embedded()) {
|
||||
ERR_FAIL_INDEX_MSG(submenu_over, items.size(), vformat("Invalid submenu index %d in _close_suspended_timeout.", submenu_over));
|
||||
if (items[submenu_over].submenu->is_visible()) {
|
||||
items[submenu_over].submenu->_close_or_suspend();
|
||||
}
|
||||
}
|
||||
if (active_submenu_index != -1 && items[active_submenu_index].submenu->is_visible()) {
|
||||
// Closes the submenu if the mouse is moved off the parent item toward the submenu,
|
||||
// but comes to a stop before reaching the submenu and the timeout is reached.
|
||||
items[active_submenu_index].submenu->_close_pressed();
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::_submenu_hidden() {
|
||||
// Ensure the submenu_timer is not running to avoid any race conditions between opening and closing submenus.
|
||||
ERR_FAIL_COND_MSG(!submenu_timer->is_stopped(), "The submenu_timer should never be running when the _submenu_hidden signal is emitted.");
|
||||
ERR_FAIL_COND_MSG(active_submenu_index == -1, "The active_submenu_index should never be -1 when _submenu_hidden is entered.");
|
||||
active_submenu_index = -1;
|
||||
submenu_over = -1;
|
||||
submenu_mouse_exited_ticks_msec = -1;
|
||||
mouse_movement_was_tested = false;
|
||||
close_was_suspended = false;
|
||||
queue_accessibility_update();
|
||||
control->queue_redraw();
|
||||
if (!activated_by_keyboard) {
|
||||
Point2 mouse_pos = is_embedded() ? get_mouse_position() : Point2(DisplayServer::get_singleton()->mouse_get_position() - get_position());
|
||||
_mouse_over_update(mouse_pos);
|
||||
}
|
||||
}
|
||||
|
||||
void PopupMenu::toggle_item_checked(int p_idx) {
|
||||
ERR_FAIL_INDEX(p_idx, items.size());
|
||||
items.write[p_idx].checked = !items[p_idx].checked;
|
||||
@@ -2917,7 +3005,7 @@ void PopupMenu::set_submenu_popup_delay(float p_time) {
|
||||
if (p_time <= 0) {
|
||||
p_time = 0.01;
|
||||
}
|
||||
|
||||
submenu_timer_popup_delay = p_time;
|
||||
submenu_timer->set_wait_time(p_time);
|
||||
}
|
||||
|
||||
@@ -3298,7 +3386,6 @@ void PopupMenu::set_visible(bool p_visible) {
|
||||
|
||||
PopupMenu::PopupMenu() {
|
||||
set_flag(FLAG_TRANSPARENT, true);
|
||||
|
||||
// The panel used to draw the panel style.
|
||||
panel = memnew(PanelContainer);
|
||||
panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
@@ -3319,16 +3406,16 @@ PopupMenu::PopupMenu() {
|
||||
control->connect(SceneStringName(draw), callable_mp(this, &PopupMenu::_draw_items));
|
||||
|
||||
submenu_timer = memnew(Timer);
|
||||
submenu_timer->set_wait_time(0.3);
|
||||
submenu_timer->set_wait_time(submenu_timer_popup_delay); // Default is 0.2.
|
||||
submenu_timer->set_one_shot(true);
|
||||
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
|
||||
add_child(submenu_timer, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
minimum_lifetime_timer = memnew(Timer);
|
||||
minimum_lifetime_timer->set_wait_time(0.3);
|
||||
minimum_lifetime_timer->set_one_shot(true);
|
||||
minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
|
||||
add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT);
|
||||
close_suspended_timer = memnew(Timer);
|
||||
close_suspended_timer->set_wait_time(CLOSE_SUSPENDED_TIMER_DELAY);
|
||||
close_suspended_timer->set_one_shot(true);
|
||||
close_suspended_timer->connect("timeout", callable_mp(this, &PopupMenu::_close_suspended_timeout));
|
||||
add_child(close_suspended_timer, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
property_helper.setup_for_instance(base_property_helper, this);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user