From f7f6432af6a8f323363bc4a72f83b1038731500b Mon Sep 17 00:00:00 2001 From: Michael Alexsander Date: Mon, 29 Apr 2024 01:09:58 -0300 Subject: [PATCH] Make `PopupMenu/Panel` shadows properly visible again --- doc/classes/PopupMenu.xml | 2 + doc/classes/PopupPanel.xml | 4 + scene/gui/popup.cpp | 150 ++++++++++++++++++++++--- scene/gui/popup.h | 9 +- scene/gui/popup_menu.cpp | 221 +++++++++++++++++++++++++++++-------- scene/gui/popup_menu.h | 8 ++ scene/main/viewport.cpp | 11 +- scene/main/window.h | 2 +- 8 files changed, 336 insertions(+), 71 deletions(-) diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 9fd47cf7f5df..8c14081366c2 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -649,6 +649,8 @@ If set to one of the values of [enum NativeMenu.SystemMenus], this [PopupMenu] is bound to the special system menu. Only one [PopupMenu] can be bound to each special menu at a time. + + diff --git a/doc/classes/PopupPanel.xml b/doc/classes/PopupPanel.xml index b581f32686cd..6967ed882ff3 100644 --- a/doc/classes/PopupPanel.xml +++ b/doc/classes/PopupPanel.xml @@ -8,6 +8,10 @@ + + + + [StyleBox] for the background panel. diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 10fadeeba1e5..4697fb6be989 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -30,9 +30,8 @@ #include "popup.h" -#include "core/config/engine.h" -#include "core/os/keyboard.h" #include "scene/gui/panel.h" +#include "scene/resources/style_box_flat.h" #include "scene/theme/theme_db.h" void Popup::_input_from_window(const Ref &p_event) { @@ -222,6 +221,24 @@ Popup::Popup() { Popup::~Popup() { } +void PopupPanel::_input_from_window(const Ref &p_event) { + if (p_event.is_valid()) { + if (!get_flag(FLAG_POPUP)) { + return; + } + + Ref b = p_event; + // Hide it if the shadows have been clicked. + if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && !panel->get_global_rect().has_point(b->get_position())) { + _close_pressed(); + } + } else { + WARN_PRINT_ONCE("PopupPanel has received an invalid InputEvent. Consider filtering out invalid events."); + } + + Popup::_input_from_window(p_event); +} + Size2 PopupPanel::_get_contents_minimum_size() const { Size2 ms; @@ -239,17 +256,77 @@ Size2 PopupPanel::_get_contents_minimum_size() const { ms = cms.max(ms); } + // Take shadows into account. + ms.width += panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT); + ms.height += panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM); + return ms + theme_cache.panel_style->get_minimum_size(); } -void PopupPanel::_update_child_rects() { +Rect2i PopupPanel::_popup_adjust_rect() const { + Rect2i current = Popup::_popup_adjust_rect(); + if (current == Rect2i()) { + return current; + } + + pre_popup_rect = current; + + _update_shadow_offsets(); + _update_child_rects(); + + if (is_layout_rtl()) { + current.position -= Vector2(ABS(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } else { + current.position -= Vector2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } + current.size += Vector2(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)) * get_content_scale_factor(); + + return current; +} + +void PopupPanel::_update_shadow_offsets() const { + if (!DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + const Ref sb = theme_cache.panel_style; + if (sb.is_null()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + const int shadow_size = sb->get_shadow_size(); + if (shadow_size == 0) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + // Offset the background panel so it leaves space inside the window for the shadows to be drawn. + const Point2 shadow_offset = sb->get_shadow_offset(); + if (is_layout_rtl()) { + panel->set_offset(SIDE_LEFT, shadow_size + shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size + shadow_offset.x); + } else { + panel->set_offset(SIDE_LEFT, shadow_size - shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size - shadow_offset.x); + } + panel->set_offset(SIDE_TOP, shadow_size - shadow_offset.y); + panel->set_offset(SIDE_BOTTOM, -shadow_size - shadow_offset.y); +} + +void PopupPanel::_update_child_rects() const { Vector2 cpos(theme_cache.panel_style->get_offset()); - Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor(); - Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size(); + cpos += Vector2(is_layout_rtl() ? -panel->get_offset(SIDE_RIGHT) : panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)); + + Vector2 csize = Vector2(get_size()) / get_content_scale_factor() - theme_cache.panel_style->get_minimum_size(); + // Trim shadows. + csize.width -= panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT); + csize.height -= panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to(get_child(i)); - if (!c) { + if (!c || c == panel) { continue; } @@ -257,26 +334,68 @@ void PopupPanel::_update_child_rects() { continue; } - if (c == panel) { - c->set_position(Vector2()); - c->set_size(panel_size); - } else { - c->set_position(cpos); - c->set_size(csize); - } + c->set_position(cpos); + c->set_size(csize); } } void PopupPanel::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: + case NOTIFICATION_ENTER_TREE: { + if (!Engine::get_singleton()->is_editor_hint() && !DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + Ref sb = theme_cache.panel_style; + if (sb.is_valid() && (sb->get_shadow_size() > 0 || sb->get_corner_radius(CORNER_TOP_LEFT) > 0 || sb->get_corner_radius(CORNER_TOP_RIGHT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_LEFT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_RIGHT) > 0)) { + WARN_PRINT_ONCE("The current theme styles PopupPanel to have shadows and/or rounded corners, but those won't display correctly if 'display/window/per_pixel_transparency/allowed' isn't enabled in the Project Settings, nor if it isn't supported."); + } + } + } break; + case NOTIFICATION_THEME_CHANGED: { panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style); + + if (is_visible()) { + _update_shadow_offsets(); + } + _update_child_rects(); } break; + case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + if (is_visible()) { + _update_shadow_offsets(); + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + // Remove the extra space used by the shadows, so they can be ignored when the popup is hidden. + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + _update_child_rects(); + + if (pre_popup_rect != Rect2i()) { + set_position(pre_popup_rect.position); + set_size(pre_popup_rect.size); + + pre_popup_rect = Rect2i(); + } + } else if (pre_popup_rect == Rect2i()) { + // The popup was made visible directly (without `popup_*()`), so just update the offsets without touching the rect. + _update_shadow_offsets(); + _update_child_rects(); + } + } break; + case NOTIFICATION_WM_SIZE_CHANGED: { _update_child_rects(); + + if (is_visible()) { + const Vector2i offsets = Vector2i(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)); + // Check if the size actually changed. + if (pre_popup_rect.size + offsets != get_size()) { + // Play safe, and stick with the new size. + pre_popup_rect = Rect2i(); + } + } } break; } } @@ -286,6 +405,9 @@ void PopupPanel::_bind_methods() { } PopupPanel::PopupPanel() { + set_flag(FLAG_TRANSPARENT, true); + panel = memnew(Panel); + panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); add_child(panel, false, INTERNAL_MODE_FRONT); } diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 69a81ad98c7e..cfebdcb07229 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -85,8 +85,15 @@ class PopupPanel : public Popup { Ref panel_style; } theme_cache; + mutable Rect2i pre_popup_rect; + protected: - void _update_child_rects(); + virtual void _input_from_window(const Ref &p_event) override; + + virtual Rect2i _popup_adjust_rect() const override; + + void _update_shadow_offsets() const; + void _update_child_rects() const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 13cfa3d2304a..1f3991998a13 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -35,9 +35,9 @@ #include "core/input/input.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/string/translation.h" #include "scene/gui/menu_bar.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/style_box_flat.h" #include "scene/theme/theme_db.h" HashMap PopupMenu::system_menus; @@ -223,6 +223,9 @@ Size2 PopupMenu::_get_item_icon_size(int p_idx) const { Size2 PopupMenu::_get_contents_minimum_size() const { Size2 minsize = theme_cache.panel_style->get_minimum_size(); minsize.width += scroll_container->get_v_scroll_bar()->get_size().width; + // Take shadows into account. + minsize.width += panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT); + minsize.height += panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM); float max_w = 0.0; float icon_w = 0.0; @@ -313,17 +316,31 @@ int PopupMenu::_get_items_total_height() const { } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { + // Make the item area exclude shadows and the vertical margins and scrollbar. + Rect2 item_clickable_area = panel->get_global_rect(); + if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) { + const int scroll_width = scroll_container->get_v_scroll_bar()->get_size().width; + if (is_layout_rtl()) { + item_clickable_area.position.x += scroll_width; + item_clickable_area.size.width -= scroll_width; + } + item_clickable_area.size.width -= scroll_width; + } float win_scale = get_content_scale_factor(); - if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) { + item_clickable_area.position.y = (item_clickable_area.position.y + theme_cache.panel_style->get_margin(SIDE_TOP)) * win_scale; + item_clickable_area.size.y -= theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); + item_clickable_area.size *= win_scale; + + if (p_over.x < item_clickable_area.position.x || p_over.x >= item_clickable_area.position.x + item_clickable_area.size.width || + p_over.y < item_clickable_area.position.y || p_over.y >= item_clickable_area.position.y + item_clickable_area.size.height) { return -1; } - Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale; - + float ofs = item_clickable_area.position.y + theme_cache.v_separation * 0.5; for (int i = 0; i < items.size(); i++) { - ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; - ofs.y += _get_item_height(i) * win_scale; - if (p_over.y - control->get_position().y * win_scale < ofs.y) { + ofs += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; + ofs += _get_item_height(i) * win_scale; + if (p_over.y - control->get_position().y * win_scale < ofs) { return i; } } @@ -332,35 +349,43 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { } void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { - Popup *submenu_popup = items[p_over].submenu; + PopupMenu *submenu_popup = items[p_over].submenu; if (submenu_popup->is_visible()) { return; // Already visible. } - Point2 this_pos = get_position(); + 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)).abs() * 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()); - float scroll_offset = control->get_position().y; - float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor(); - float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor(); + 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; submenu_popup->reset_size(); // Shrink the popup size to its contents. - Size2 submenu_size = submenu_popup->get_size(); + const Size2 submenu_size = submenu_popup->get_size(); - Point2 submenu_pos; - if (control->is_layout_rtl()) { - submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); - } else { - submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); - } + // 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()); + Rect2i screen_rect = is_embedded() ? Rect2i(get_embedder()->get_visible_rect()) : get_parent_rect(); + 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); + if (submenu_pos.x < screen_rect.position.x) { + submenu_pos.x = this_pos.x + this_rect.size.width - panel_ofs_start.x; + } - // 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; - } + 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); + 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; + } - if (submenu_pos.x + submenu_size.width > get_parent_rect().position.x + get_parent_rect().size.width) { - submenu_pos.x = this_pos.x - submenu_size.width; + this_rect.position.x += panel_ofs_start.x; } submenu_popup->set_position(submenu_pos); @@ -387,9 +412,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. - Rect2 safe_area = this_rect; - safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = scaled_height_cache + theme_cache.v_separation; + const Rect2 safe_area(get_position(), get_size()); Viewport *vp = submenu_popup->get_embedder(); if (vp) { vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area); @@ -397,16 +420,18 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); } - // Make the position of the parent popup relative to submenu popup. - this_rect.position = this_rect.position - submenu_pum->get_position(); + 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, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); + 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) { - int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; + 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)); } } @@ -446,7 +471,7 @@ void PopupMenu::_input_from_window(const Ref &p_event) { if (p_event.is_valid()) { _input_from_window_internal(p_event); } else { - WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering invalid events out."); + WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering out invalid events."); } Popup::_input_from_window(p_event); } @@ -570,15 +595,19 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { } } - // Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar. - Rect2 item_clickable_area = scroll_container->get_rect(); + // Make the item area exclude shadows and the vertical margins and scrollbar. + Rect2 item_clickable_area = panel->get_global_rect(); if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) { + int scroll_width = scroll_container->get_v_scroll_bar()->get_size().width; if (is_layout_rtl()) { - item_clickable_area.position.x += scroll_container->get_v_scroll_bar()->get_size().width; + item_clickable_area.position.x += scroll_width; + item_clickable_area.size.width -= scroll_width; } - item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; + item_clickable_area.size.width -= scroll_width; } - item_clickable_area.size = item_clickable_area.size * get_content_scale_factor(); + item_clickable_area.position.y = (item_clickable_area.position.y + theme_cache.panel_style->get_margin(SIDE_TOP)) * get_content_scale_factor(); + item_clickable_area.size.y -= theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); + item_clickable_area.size *= get_content_scale_factor(); Ref b = p_event; @@ -592,9 +621,16 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { during_grabbed_click = false; is_scrolling = is_layout_rtl() ? b->get_position().x < item_clickable_area.position.x : b->get_position().x > item_clickable_area.size.width; + // Hide it if the shadows have been clicked. + if (get_flag(FLAG_POPUP) && !panel->get_global_rect().has_point(b->get_position())) { + _close_pressed(); + return; + } + if (!item_clickable_area.has_point(b->get_position())) { return; } + _mouse_over_update(b->get_position()); } else { if (is_scrolling) { @@ -608,6 +644,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { if (!item_clickable_area.has_point(b->get_position())) { return; } + // Disable clicks under a time threshold to avoid selection right when opening the popup. if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 400) { return; @@ -643,7 +680,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { activated_by_keyboard = false; for (const Rect2 &E : autohide_areas) { - if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) { + 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; @@ -655,7 +692,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { minimum_lifetime_timer->stop(); } - if (!item_clickable_area.has_point(m->get_position())) { + if (mouse_over == -1 && !item_clickable_area.has_point(m->get_position())) { return; } _mouse_over_update(m->get_position()); @@ -971,6 +1008,57 @@ void PopupMenu::_menu_changed() { emit_signal(SNAME("menu_changed")); } +void PopupMenu::_update_shadow_offsets() const { + if (!DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + Ref sb = theme_cache.panel_style; + if (sb.is_null()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + const int shadow_size = sb->get_shadow_size(); + if (shadow_size == 0) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + // Offset the background panel so it leaves space inside the window for the shadows to be drawn. + const Point2 shadow_offset = sb->get_shadow_offset(); + if (is_layout_rtl()) { + panel->set_offset(SIDE_LEFT, shadow_size + shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size + shadow_offset.x); + } else { + panel->set_offset(SIDE_LEFT, shadow_size - shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size - shadow_offset.x); + } + panel->set_offset(SIDE_TOP, shadow_size - shadow_offset.y); + panel->set_offset(SIDE_BOTTOM, -shadow_size - shadow_offset.y); +} + +Rect2i PopupMenu::_popup_adjust_rect() const { + Rect2i current = Popup::_popup_adjust_rect(); + if (current == Rect2i()) { + return current; + } + + pre_popup_rect = current; + + _update_shadow_offsets(); + + if (is_layout_rtl()) { + current.position -= Vector2(ABS(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } else { + current.position -= Vector2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } + current.size += Vector2(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)) * get_content_scale_factor(); + + return current; +} + void PopupMenu::add_child_notify(Node *p_child) { Window::add_child_notify(p_child); @@ -1018,6 +1106,13 @@ void PopupMenu::_notification(int p_what) { if (system_menu_id != NativeMenu::INVALID_MENU_ID) { bind_global_menu(); } + + if (!Engine::get_singleton()->is_editor_hint() && !DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + Ref sb = theme_cache.panel_style; + if (sb.is_valid() && (sb->get_shadow_size() > 0 || sb->get_corner_radius(CORNER_TOP_LEFT) > 0 || sb->get_corner_radius(CORNER_TOP_RIGHT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_LEFT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_RIGHT) > 0)) { + WARN_PRINT_ONCE("The current theme styles PopupMenu to have shadows and/or rounded corners, but those won't display correctly if 'display/window/per_pixel_transparency/allowed' isn't enabled in the Project Settings, nor if it isn't supported."); + } + } } break; case NOTIFICATION_EXIT_TREE: { @@ -1026,12 +1121,16 @@ void PopupMenu::_notification(int p_what) { } } break; + case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - scroll_container->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style); + panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style); + + if (is_visible()) { + _update_shadow_offsets(); + } [[fallthrough]]; } - case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { NativeMenu *nmenu = NativeMenu::get_singleton(); bool is_global = global_menu.is_valid(); @@ -1064,6 +1163,17 @@ void PopupMenu::_notification(int p_what) { } } break; + case NOTIFICATION_WM_SIZE_CHANGED: { + if (is_visible()) { + const Vector2i offsets = Vector2i(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)); + // Check if the size actually changed. + if (pre_popup_rect.size + offsets != get_size()) { + // Play safe, and stick with the new size. + pre_popup_rect = Rect2i(); + } + } + } break; + case NOTIFICATION_POST_POPUP: { popup_time_msec = OS::get_singleton()->get_ticks_msec(); initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); @@ -1176,10 +1286,25 @@ void PopupMenu::_notification(int p_what) { } set_process_internal(false); + + // Remove the extra space used by the shadows, so they can be ignored when the popup is hidden. + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + + if (pre_popup_rect != Rect2i()) { + set_position(pre_popup_rect.position); + set_size(pre_popup_rect.size); + + pre_popup_rect = Rect2i(); + } } else { if (!is_embedded()) { set_process_internal(true); } + + // The popup was made visible directly (without `popup_*()`), so just update the offsets without touching the rect. + if (pre_popup_rect == Rect2i()) { + _update_shadow_offsets(); + } } } break; } @@ -2868,11 +2993,17 @@ 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); + add_child(panel, false, INTERNAL_MODE_FRONT); + // Scroll Container scroll_container = memnew(ScrollContainer); scroll_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - scroll_container->set_clip_contents(true); - add_child(scroll_container, false, INTERNAL_MODE_FRONT); + panel->add_child(scroll_container, false, INTERNAL_MODE_FRONT); // The control which will display the items control = memnew(Control); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 442ffa2ebc94..2a5537e7f975 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -37,6 +37,8 @@ #include "scene/property_list_helper.h" #include "scene/resources/text_line.h" +class PanelContainer; + class PopupMenu : public Popup { GDCLASS(PopupMenu, Popup); @@ -94,6 +96,9 @@ class PopupMenu : public Popup { Item(bool p_dummy) {} }; + mutable Rect2i pre_popup_rect; + void _update_shadow_offsets() const; + static inline PropertyListHelper base_property_helper; PropertyListHelper property_helper; @@ -149,6 +154,7 @@ class PopupMenu : public Popup { uint64_t search_time_msec = 0; String search_string = ""; + PanelContainer *panel = nullptr; ScrollContainer *scroll_container = nullptr; Control *control = nullptr; @@ -211,6 +217,8 @@ class PopupMenu : public Popup { int _get_item_checkable_type(int p_index) const; protected: + virtual Rect2i _popup_adjust_rect() const override; + virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; virtual void _input_from_window(const Ref &p_event) override; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 76a449c77d52..da955e3cc7d0 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1475,9 +1475,6 @@ void Viewport::_gui_show_tooltip() { PopupPanel *panel = memnew(PopupPanel); panel->set_theme_type_variation(SNAME("TooltipPanel")); - // Ensure no opaque background behind the panel as its StyleBox can be partially transparent (e.g. corners). - panel->set_transparent_background(true); - // If no custom tooltip is given, use a default implementation. if (!base_tooltip) { gui.tooltip_label = memnew(Label); @@ -1490,12 +1487,9 @@ void Viewport::_gui_show_tooltip() { base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - panel->set_transient(true); panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_flag(Window::FLAG_POPUP, false); panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); - // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport. - panel->set_flag(Window::FLAG_TRANSPARENT, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; @@ -1547,12 +1541,9 @@ void Viewport::_gui_show_tooltip() { r.position.y = vr.position.y; } - gui.tooltip_popup->set_position(r.position); - gui.tooltip_popup->set_size(r.size); - DisplayServer::WindowID active_popup = DisplayServer::get_singleton()->window_get_active_popup(); if (active_popup == DisplayServer::INVALID_WINDOW_ID || active_popup == window->get_window_id()) { - gui.tooltip_popup->show(); + gui.tooltip_popup->popup(r); } gui.tooltip_popup->child_controls_changed(); } diff --git a/scene/main/window.h b/scene/main/window.h index 6c82efcc3f9b..649e079c4676 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -118,7 +118,7 @@ class Window : public Viewport { String title; String tr_title; mutable int current_screen = 0; - mutable Vector2i position; + mutable Point2i position; mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE); mutable Size2i min_size; mutable Size2i max_size;