diff options
Diffstat (limited to 'scene/gui/popup_menu.cpp')
-rw-r--r-- | scene/gui/popup_menu.cpp | 151 |
1 files changed, 102 insertions, 49 deletions
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index d7139d0140..6d43bbdcf3 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -67,7 +67,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.width += items[i].h_ofs; - if (items[i].checkable_type) { + if (items[i].checkable_type && !items[i].separator) { has_check = true; } @@ -111,7 +111,7 @@ int PopupMenu::_get_item_height(int p_item) const { ERR_FAIL_COND_V(p_item < 0, 0); int icon_height = items[p_item].get_icon_size().height; - if (items[p_item].checkable_type) { + if (items[p_item].checkable_type && !items[p_item].separator) { icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height())); } @@ -192,7 +192,7 @@ void PopupMenu::_activate_submenu(int p_over) { Popup *submenu_popup = Object::cast_to<Popup>(n); ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + "."); if (submenu_popup->is_visible()) { - return; //already visible! + return; // Already visible. } Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); @@ -203,15 +203,17 @@ void PopupMenu::_activate_submenu(int p_over) { float scroll_offset = control->get_position().y; - Point2 submenu_pos; + submenu_popup->set_as_minsize(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); + + Point2 submenu_pos; if (control->is_layout_rtl()) { submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset); } else { submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset); } - // Fix pos if going outside parent rect + // Fix pos if going outside parent rect. if (submenu_pos.x < get_parent_rect().position.x) { submenu_pos.x = this_pos.x + submenu_size.width; } @@ -222,25 +224,33 @@ void PopupMenu::_activate_submenu(int p_over) { submenu_popup->set_close_on_parent_focus(false); submenu_popup->set_position(submenu_pos); - submenu_popup->set_as_minsize(); // Shrink the popup size to its contents. - submenu_popup->popup(); - // Set autohide areas PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup); - if (submenu_pum) { - submenu_pum->take_mouse_focus(); - // Make the position of the parent popup relative to submenu popup - this_rect.position = this_rect.position - submenu_pum->get_position(); - - // 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, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2)); - - // If there is an area below the submenu item, add an autohide area there. - if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height; - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); - } + if (!submenu_pum) { + submenu_popup->popup(); + return; + } + + // If not triggered by the mouse, start the popup with its first item selected. + if (submenu_pum->get_item_count() > 0 && Input::get_singleton()->is_action_just_pressed("ui_accept")) { + submenu_pum->set_current_index(0); + } + + submenu_pum->popup(); + + // Set autohide areas. + + // Make the position of the parent popup relative to submenu popup. + this_rect.position = this_rect.position - submenu_pum->get_position(); + + // 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, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2)); + + // If there is an area below the submenu item, add an autohide area there. + if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { + int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height; + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -486,7 +496,7 @@ void PopupMenu::_draw_items() { bool rtl = control->is_layout_rtl(); Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover")); - // In Item::checkable_type enum order (less the non-checkable member) + // In Item::checkable_type enum order (less the non-checkable member). Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")) }; Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")) }; Ref<Texture2D> submenu; @@ -515,6 +525,10 @@ void PopupMenu::_draw_items() { float icon_ofs = 0.0; bool has_check = false; for (int i = 0; i < items.size(); i++) { + if (items[i].separator) { + continue; + } + icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs); if (items[i].checkable_type) { @@ -558,29 +572,33 @@ void PopupMenu::_draw_items() { if (items[i].separator) { int sep_h = separator->get_center_size().height + separator->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - if (!text.is_empty()) { - int text_size = items[i].text_buf->get_size().width; - int text_center = display_width / 2; - int text_left = text_center - text_size / 2; - int text_right = text_center + text_size / 2; - if (text_left > item_ofs.x) { - labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, text_left - item_ofs.x), sep_h))); + if (!text.is_empty() || !items[i].icon.is_null()) { + int content_size = items[i].text_buf->get_size().width; + if (!items[i].icon.is_null()) { + content_size += icon_size.width + hseparation; } - if (text_right < display_width) { - labeled_separator_right->draw(ci, Rect2(Point2(text_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - text_right), sep_h))); + + int content_center = display_width / 2; + int content_left = content_center - content_size / 2; + int content_right = content_center + content_size / 2; + if (content_left > item_ofs.x) { + labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h))); + } + if (content_right < display_width) { + labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); } } else { separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); } } - Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1); // For non-separator items, add some padding for the content. item_ofs.x += item_start_padding; // Checkboxes - if (items[i].checkable_type) { + if (items[i].checkable_type && !items[i].separator) { Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr(); if (rtl) { icon->draw(ci, Size2(control->get_size().width - item_ofs.x - icon->get_width(), item_ofs.y) + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color); @@ -589,16 +607,28 @@ void PopupMenu::_draw_items() { } } + int separator_ofs = (display_width - items[i].text_buf->get_size().width) / 2; + // Icon if (!items[i].icon.is_null()) { - if (rtl) { - items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + if (items[i].separator) { + separator_ofs -= (icon_size.width + hseparation) / 2; + + if (rtl) { + items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } else { + items[i].icon->draw(ci, item_ofs + Size2(separator_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } } else { - items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + if (rtl) { + items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } else { + items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } } } - // Submenu arrow on right hand side + // Submenu arrow on right hand side. if (!items[i].submenu.is_empty()) { if (rtl) { submenu->draw(ci, Point2(scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); @@ -612,8 +642,11 @@ void PopupMenu::_draw_items() { int outline_size = get_theme_constant(SNAME("outline_size")); if (items[i].separator) { if (!text.is_empty()) { - int center = (display_width - items[i].text_buf->get_size().width) / 2; - Vector2 text_pos = Point2(center, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); + Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); + if (!rtl && !items[i].icon.is_null()) { + text_pos.x += icon_size.width + hseparation; + } + if (outline_size > 0 && font_outline_color.a > 0) { items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); } @@ -650,7 +683,7 @@ void PopupMenu::_draw_items() { items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color); } - // Cache the item vertical offset from the first item and the height + // Cache the item vertical offset from the first item and the height. items.write[i]._ofs_cache = ofs.y; items.write[i]._height_cache = h; @@ -715,13 +748,32 @@ void PopupMenu::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent()); if (pm) { - // Inherit submenu's popup delay time from parent menu + // Inherit submenu's popup delay time from parent menu. float pm_delay = pm->get_submenu_popup_delay(); set_submenu_popup_delay(pm_delay); } } break; case NOTIFICATION_THEME_CHANGED: - case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + // Pass the layout direction to all submenus. + for (int i = 0; i < items.size(); i++) { + if (items[i].submenu.is_empty()) { + continue; + } + + Node *n = get_node(items[i].submenu); + if (!n) { + continue; + } + + PopupMenu *pm = Object::cast_to<PopupMenu>(n); + if (pm) { + pm->set_layout_direction(get_layout_direction()); + } + } + + [[fallthrough]]; + } case NOTIFICATION_TRANSLATION_CHANGED: { for (int i = 0; i < items.size(); i++) { items.write[i].xl_text = atr(items[i].text); @@ -810,7 +862,7 @@ void PopupMenu::_notification(int p_what) { #define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \ item.text = p_label; \ item.xl_text = atr(p_label); \ - item.id = p_id == -1 ? items.size() : p_id; \ + item.id = p_id == -1 ? items.size() - 1 : p_id; \ item.accel = p_accel; void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { @@ -892,7 +944,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int _ref_shortcut(p_shortcut); \ item.text = p_shortcut->get_name(); \ item.xl_text = atr(item.text); \ - item.id = p_id == -1 ? items.size() : p_id; \ + item.id = p_id == -1 ? items.size() - 1 : p_id; \ item.shortcut = p_shortcut; \ item.shortcut_is_global = p_global; @@ -961,7 +1013,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, Item item; item.text = p_label; item.xl_text = atr(p_label); - item.id = p_id == -1 ? items.size() : p_id; + item.id = p_id == -1 ? items.size() - 1 : p_id; item.submenu = p_submenu; items.push_back(item); _shape_item(items.size() - 1); @@ -1403,12 +1455,12 @@ void PopupMenu::activate_item(int p_item) { need_hide = false; } - emit_signal(SNAME("id_pressed"), id); - emit_signal(SNAME("index_pressed"), p_item); - if (need_hide) { hide(); } + + emit_signal(SNAME("id_pressed"), id); + emit_signal(SNAME("index_pressed"), p_item); } void PopupMenu::remove_item(int p_idx) { @@ -1747,6 +1799,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut); + ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index); ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index); ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count); |