Browse Source

Fix Modal Dialog with Embedded Game

Hilderin 6 months ago
parent
commit
a9e06b8f64

+ 64 - 27
editor/plugins/embedded_process.cpp

@@ -67,20 +67,10 @@ void EmbeddedProcess::_notification(int p_what) {
 		} break;
 		} break;
 		case NOTIFICATION_APPLICATION_FOCUS_IN: {
 		case NOTIFICATION_APPLICATION_FOCUS_IN: {
 			application_has_focus = true;
 			application_has_focus = true;
-			if (embedded_process_was_focused) {
-				embedded_process_was_focused = false;
-				// Refocus the embedded process if it was focused when the application lost focus,
-				// but do not refocus if the embedded process is currently focused (indicating it just lost focus)
-				// or if the current window is a different popup or secondary window.
-				if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) {
-					grab_focus();
-					queue_update_embedded_process();
-				}
-			}
+			last_application_focus_time = OS::get_singleton()->get_ticks_msec();
 		} break;
 		} break;
 		case NOTIFICATION_APPLICATION_FOCUS_OUT: {
 		case NOTIFICATION_APPLICATION_FOCUS_OUT: {
 			application_has_focus = false;
 			application_has_focus = false;
-			embedded_process_was_focused = embedding_completed && current_process_id == focused_process_id;
 		} break;
 		} break;
 	}
 	}
 }
 }
@@ -286,14 +276,27 @@ void EmbeddedProcess::_check_mouse_over() {
 	// This method checks if the mouse is over the embedded process while the current application is focused.
 	// This method checks if the mouse is over the embedded process while the current application is focused.
 	// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
 	// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
 	// allowing the user to interact with it immediately without needing to click first.
 	// allowing the user to interact with it immediately without needing to click first.
-	if (!is_visible_in_tree() || !embedding_completed || !application_has_focus || !window || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
+	if (!embedding_completed || !application_has_focus || !window || has_focus() || !is_visible_in_tree() || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
+		return;
+	}
+
+	// Before checking whether the mouse is truly inside the embedded process, ensure
+	// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
+	// `_check_mouse_over` may be triggered before the editor hides the game workspace.
+	// This prevents the embedded process from regaining focus immediately after the editor has taken it.
+	if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
 		return;
 		return;
 	}
 	}
 
 
-	bool focused = has_focus();
+	// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
+	// while the floating game window is being resized.
+	BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
+	if (!mouse_button_mask.is_empty()) {
+		return;
+	}
 
 
 	// Not stealing focus from a textfield.
 	// Not stealing focus from a textfield.
-	if (!focused && get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
+	if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
 		return;
 		return;
 	}
 	}
 
 
@@ -309,14 +312,17 @@ void EmbeddedProcess::_check_mouse_over() {
 		return;
 		return;
 	}
 	}
 
 
-	// When we already have the focus and the user moves the mouse over the embedded process,
-	// we just need to refocus the process.
-	if (focused) {
-		queue_update_embedded_process();
-	} else {
-		grab_focus();
-		queue_redraw();
+	// When there's a modal window, we don't want to grab the focus to prevent
+	// the game window to go in front of the modal window.
+	if (_get_current_modal_window()) {
+		return;
 	}
 	}
+
+	// Force "regrabbing" the game window focus.
+	last_updated_embedded_process_focused = false;
+
+	grab_focus();
+	queue_redraw();
 }
 }
 
 
 void EmbeddedProcess::_check_focused_process_id() {
 void EmbeddedProcess::_check_focused_process_id() {
@@ -325,17 +331,48 @@ void EmbeddedProcess::_check_focused_process_id() {
 		focused_process_id = process_id;
 		focused_process_id = process_id;
 		if (focused_process_id == current_process_id) {
 		if (focused_process_id == current_process_id) {
 			// The embedded process got the focus.
 			// The embedded process got the focus.
-			emit_signal(SNAME("embedded_process_focused"));
-			if (has_focus()) {
-				// Redraw to updated the focus style.
-				queue_redraw();
-			} else {
-				grab_focus();
+
+			// Refocus the current model when focusing the embedded process.
+			Window *modal_window = _get_current_modal_window();
+			if (!modal_window) {
+				emit_signal(SNAME("embedded_process_focused"));
+				if (has_focus()) {
+					// Redraw to updated the focus style.
+					queue_redraw();
+				} else {
+					grab_focus();
+				}
 			}
 			}
 		} else if (has_focus()) {
 		} else if (has_focus()) {
 			release_focus();
 			release_focus();
 		}
 		}
 	}
 	}
+
+	// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
+	if (!application_has_focus && focused_process_id == current_process_id) {
+		Window *modal_window = _get_current_modal_window();
+		if (modal_window) {
+			if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
+				modal_window->set_mode(Window::MODE_WINDOWED);
+			}
+			callable_mp(modal_window, &Window::grab_focus).call_deferred();
+		}
+	}
+}
+
+Window *EmbeddedProcess::_get_current_modal_window() {
+	Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
+	for (const DisplayServer::WindowID &window_id : wl) {
+		Window *w = Window::get_from_id(window_id);
+		if (!w) {
+			continue;
+		}
+
+		if (w->is_exclusive()) {
+			return w;
+		}
+	}
+	return nullptr;
 }
 }
 
 
 void EmbeddedProcess::_bind_methods() {
 void EmbeddedProcess::_bind_methods() {

+ 2 - 1
editor/plugins/embedded_process.h

@@ -37,7 +37,7 @@ class EmbeddedProcess : public Control {
 	GDCLASS(EmbeddedProcess, Control);
 	GDCLASS(EmbeddedProcess, Control);
 
 
 	bool application_has_focus = true;
 	bool application_has_focus = true;
-	bool embedded_process_was_focused = false;
+	uint64_t last_application_focus_time = 0;
 	OS::ProcessID focused_process_id = 0;
 	OS::ProcessID focused_process_id = 0;
 	OS::ProcessID current_process_id = 0;
 	OS::ProcessID current_process_id = 0;
 	bool embedding_grab_focus = false;
 	bool embedding_grab_focus = false;
@@ -68,6 +68,7 @@ class EmbeddedProcess : public Control {
 	void _check_focused_process_id();
 	void _check_focused_process_id();
 	bool _is_embedded_process_updatable();
 	bool _is_embedded_process_updatable();
 	Rect2i _get_global_embedded_window_rect();
 	Rect2i _get_global_embedded_window_rect();
+	Window *_get_current_modal_window();
 
 
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();

+ 2 - 1
platform/windows/display_server_windows.cpp

@@ -2986,7 +2986,8 @@ Error DisplayServerWindows::embed_process(WindowID p_window, OS::ProcessID p_pid
 	// (e.g., a screen to the left of the main screen).
 	// (e.g., a screen to the left of the main screen).
 	const Rect2i adjusted_rect = Rect2i(p_rect.position + _get_screens_origin(), p_rect.size);
 	const Rect2i adjusted_rect = Rect2i(p_rect.position + _get_screens_origin(), p_rect.size);
 
 
-	SetWindowPos(ep->window_handle, nullptr, adjusted_rect.position.x, adjusted_rect.position.y, adjusted_rect.size.x, adjusted_rect.size.y, SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
+	// Use HWND_BOTTOM to prevent reordering of the embedded window over another popup.
+	SetWindowPos(ep->window_handle, HWND_BOTTOM, adjusted_rect.position.x, adjusted_rect.position.y, adjusted_rect.size.x, adjusted_rect.size.y, SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
 
 
 	if (ep->is_visible != p_visible) {
 	if (ep->is_visible != p_visible) {
 		if (p_visible) {
 		if (p_visible) {