2
0
Эх сурвалжийг харах

Make `PopupMenu/Panel` shadows properly visible again

Michael Alexsander 1 жил өмнө
parent
commit
f7f6432af6

+ 2 - 0
doc/classes/PopupMenu.xml

@@ -649,6 +649,8 @@
 		<member name="system_menu_id" type="int" setter="set_system_menu" getter="get_system_menu" enum="NativeMenu.SystemMenus" default="0">
 			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.
 		</member>
+		<member name="transparent" type="bool" setter="set_flag" getter="get_flag" overrides="Window" default="true" />
+		<member name="transparent_bg" type="bool" setter="set_transparent_background" getter="has_transparent_background" overrides="Viewport" default="true" />
 	</members>
 	<signals>
 		<signal name="id_focused">

+ 4 - 0
doc/classes/PopupPanel.xml

@@ -8,6 +8,10 @@
 	</description>
 	<tutorials>
 	</tutorials>
+	<members>
+		<member name="transparent" type="bool" setter="set_flag" getter="get_flag" overrides="Window" default="true" />
+		<member name="transparent_bg" type="bool" setter="set_transparent_background" getter="has_transparent_background" overrides="Viewport" default="true" />
+	</members>
 	<theme_items>
 		<theme_item name="panel" data_type="style" type="StyleBox">
 			[StyleBox] for the background panel.

+ 136 - 14
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<InputEvent> &p_event) {
@@ -222,6 +221,24 @@ Popup::Popup() {
 Popup::~Popup() {
 }
 
+void PopupPanel::_input_from_window(const Ref<InputEvent> &p_event) {
+	if (p_event.is_valid()) {
+		if (!get_flag(FLAG_POPUP)) {
+			return;
+		}
+
+		Ref<InputEventMouseButton> 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<StyleBoxFlat> 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<Control>(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<StyleBoxFlat> 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);
 }

+ 8 - 1
scene/gui/popup.h

@@ -85,8 +85,15 @@ class PopupPanel : public Popup {
 		Ref<StyleBox> panel_style;
 	} theme_cache;
 
+	mutable Rect2i pre_popup_rect;
+
 protected:
-	void _update_child_rects();
+	virtual void _input_from_window(const Ref<InputEvent> &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();

+ 176 - 45
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<NativeMenu::SystemMenus, PopupMenu *> 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<InputEvent> &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<InputEvent> &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<InputEventMouseButton> b = p_event;
 
@@ -592,9 +621,16 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &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<InputEvent> &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<InputEvent> &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<InputEvent> &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<StyleBoxFlat> 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<StyleBoxFlat> 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);

+ 8 - 0
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<InputEvent> &p_event) override;

+ 1 - 10
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();
 }

+ 1 - 1
scene/main/window.h

@@ -118,7 +118,7 @@ private:
 	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;