Skip to content

Commit bd87c3a

Browse files
committed
Merge pull request #102247 from Hilderin/fix-embedded-game-modal-dialog
Fix Modal Dialog with Embedded Game
2 parents 622344b + a9e06b8 commit bd87c3a

File tree

3 files changed

+68
-29
lines changed

3 files changed

+68
-29
lines changed

editor/plugins/embedded_process.cpp

+64-27
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,10 @@ void EmbeddedProcess::_notification(int p_what) {
7373
} break;
7474
case NOTIFICATION_APPLICATION_FOCUS_IN: {
7575
application_has_focus = true;
76-
if (embedded_process_was_focused) {
77-
embedded_process_was_focused = false;
78-
// Refocus the embedded process if it was focused when the application lost focus,
79-
// but do not refocus if the embedded process is currently focused (indicating it just lost focus)
80-
// or if the current window is a different popup or secondary window.
81-
if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) {
82-
grab_focus();
83-
queue_update_embedded_process();
84-
}
85-
}
76+
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
8677
} break;
8778
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
8879
application_has_focus = false;
89-
embedded_process_was_focused = embedding_completed && current_process_id == focused_process_id;
9080
} break;
9181
}
9282
}
@@ -295,14 +285,27 @@ void EmbeddedProcess::_check_mouse_over() {
295285
// This method checks if the mouse is over the embedded process while the current application is focused.
296286
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
297287
// allowing the user to interact with it immediately without needing to click first.
298-
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)) {
288+
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)) {
289+
return;
290+
}
291+
292+
// Before checking whether the mouse is truly inside the embedded process, ensure
293+
// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
294+
// `_check_mouse_over` may be triggered before the editor hides the game workspace.
295+
// This prevents the embedded process from regaining focus immediately after the editor has taken it.
296+
if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
299297
return;
300298
}
301299

302-
bool focused = has_focus();
300+
// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
301+
// while the floating game window is being resized.
302+
BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
303+
if (!mouse_button_mask.is_empty()) {
304+
return;
305+
}
303306

304307
// Not stealing focus from a textfield.
305-
if (!focused && get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
308+
if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
306309
return;
307310
}
308311

@@ -318,14 +321,17 @@ void EmbeddedProcess::_check_mouse_over() {
318321
return;
319322
}
320323

321-
// When we already have the focus and the user moves the mouse over the embedded process,
322-
// we just need to refocus the process.
323-
if (focused) {
324-
queue_update_embedded_process();
325-
} else {
326-
grab_focus();
327-
queue_redraw();
324+
// When there's a modal window, we don't want to grab the focus to prevent
325+
// the game window to go in front of the modal window.
326+
if (_get_current_modal_window()) {
327+
return;
328328
}
329+
330+
// Force "regrabbing" the game window focus.
331+
last_updated_embedded_process_focused = false;
332+
333+
grab_focus();
334+
queue_redraw();
329335
}
330336

331337
void EmbeddedProcess::_check_focused_process_id() {
@@ -334,17 +340,48 @@ void EmbeddedProcess::_check_focused_process_id() {
334340
focused_process_id = process_id;
335341
if (focused_process_id == current_process_id) {
336342
// The embedded process got the focus.
337-
emit_signal(SNAME("embedded_process_focused"));
338-
if (has_focus()) {
339-
// Redraw to updated the focus style.
340-
queue_redraw();
341-
} else {
342-
grab_focus();
343+
344+
// Refocus the current model when focusing the embedded process.
345+
Window *modal_window = _get_current_modal_window();
346+
if (!modal_window) {
347+
emit_signal(SNAME("embedded_process_focused"));
348+
if (has_focus()) {
349+
// Redraw to updated the focus style.
350+
queue_redraw();
351+
} else {
352+
grab_focus();
353+
}
343354
}
344355
} else if (has_focus()) {
345356
release_focus();
346357
}
347358
}
359+
360+
// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
361+
if (!application_has_focus && focused_process_id == current_process_id) {
362+
Window *modal_window = _get_current_modal_window();
363+
if (modal_window) {
364+
if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
365+
modal_window->set_mode(Window::MODE_WINDOWED);
366+
}
367+
callable_mp(modal_window, &Window::grab_focus).call_deferred();
368+
}
369+
}
370+
}
371+
372+
Window *EmbeddedProcess::_get_current_modal_window() {
373+
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
374+
for (const DisplayServer::WindowID &window_id : wl) {
375+
Window *w = Window::get_from_id(window_id);
376+
if (!w) {
377+
continue;
378+
}
379+
380+
if (w->is_exclusive()) {
381+
return w;
382+
}
383+
}
384+
return nullptr;
348385
}
349386

350387
void EmbeddedProcess::_bind_methods() {

editor/plugins/embedded_process.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class EmbeddedProcess : public Control {
3737
GDCLASS(EmbeddedProcess, Control);
3838

3939
bool application_has_focus = true;
40-
bool embedded_process_was_focused = false;
40+
uint64_t last_application_focus_time = 0;
4141
OS::ProcessID focused_process_id = 0;
4242
OS::ProcessID current_process_id = 0;
4343
bool embedding_grab_focus = false;
@@ -68,6 +68,7 @@ class EmbeddedProcess : public Control {
6868
void _check_focused_process_id();
6969
bool _is_embedded_process_updatable();
7070
Rect2i _get_global_embedded_window_rect();
71+
Window *_get_current_modal_window();
7172

7273
protected:
7374
static void _bind_methods();

platform/windows/display_server_windows.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -2986,7 +2986,8 @@ Error DisplayServerWindows::embed_process(WindowID p_window, OS::ProcessID p_pid
29862986
// (e.g., a screen to the left of the main screen).
29872987
const Rect2i adjusted_rect = Rect2i(p_rect.position + _get_screens_origin(), p_rect.size);
29882988

2989-
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);
2989+
// Use HWND_BOTTOM to prevent reordering of the embedded window over another popup.
2990+
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);
29902991

29912992
if (ep->is_visible != p_visible) {
29922993
if (p_visible) {

0 commit comments

Comments
 (0)