Skip to content

Commit f2a42be

Browse files
committed
Fix Modal Dialog with Embedded Game
1 parent f0f5319 commit f2a42be

File tree

3 files changed

+70
-23
lines changed

3 files changed

+70
-23
lines changed

editor/plugins/embedded_process.cpp

+66-21
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,18 @@ void EmbeddedProcess::_notification(int p_what) {
6767
} break;
6868
case NOTIFICATION_APPLICATION_FOCUS_IN: {
6969
application_has_focus = true;
70-
if (embedded_process_was_focused) {
71-
embedded_process_was_focused = false;
72-
// Refocus the embedded process if it was focused when the application lost focus,
73-
// but do not refocus if the embedded process is currently focused (indicating it just lost focus)
74-
// or if the current window is a different popup or secondary window.
75-
if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) {
76-
grab_focus();
77-
queue_update_embedded_process();
78-
}
79-
}
70+
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
8071
} break;
8172
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
8273
application_has_focus = false;
83-
embedded_process_was_focused = embedding_completed && current_process_id == focused_process_id;
74+
75+
//// Refocus the current model when focusing the embedded process.
76+
//if (embedding_completed && current_process_id == DisplayServer::get_singleton()->get_focused_process_id()) {
77+
// Window *modal_window = _get_current_modal_window();
78+
// if (modal_window) {
79+
// callable_mp(modal_window, &Window::grab_focus).call_deferred();
80+
// }
81+
//}
8482
} break;
8583
}
8684
}
@@ -286,14 +284,27 @@ void EmbeddedProcess::_check_mouse_over() {
286284
// This method checks if the mouse is over the embedded process while the current application is focused.
287285
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
288286
// allowing the user to interact with it immediately without needing to click first.
289-
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)) {
287+
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)) {
290288
return;
291289
}
292290

293-
bool focused = has_focus();
291+
// Before checking whether the mouse is truly inside the embedded process, ensure
292+
// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
293+
// `_check_mouse_over` may be triggered before the editor hides the game workspace.
294+
// This prevents the embedded process from regaining focus immediately after the editor has taken it.
295+
if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
296+
return;
297+
}
298+
299+
// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
300+
// while the floating game window is being resized.
301+
BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
302+
if (!mouse_button_mask.is_empty()) {
303+
return;
304+
}
294305

295306
// Not stealing focus from a textfield.
296-
if (!focused && get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
307+
if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
297308
return;
298309
}
299310

@@ -309,14 +320,17 @@ void EmbeddedProcess::_check_mouse_over() {
309320
return;
310321
}
311322

312-
// When we already have the focus and the user moves the mouse over the embedded process,
313-
// we just need to refocus the process.
314-
if (focused) {
315-
queue_update_embedded_process();
316-
} else {
317-
grab_focus();
318-
queue_redraw();
323+
// When there's a modal window, we don't want to grab the focus to prevent
324+
// the game window to go in front of the modal window.
325+
if (_get_current_modal_window()) {
326+
return;
319327
}
328+
329+
// Force "regrabbing" the game window focus.
330+
last_updated_embedded_process_focused = false;
331+
332+
grab_focus();
333+
queue_redraw();
320334
}
321335

322336
void EmbeddedProcess::_check_focused_process_id() {
@@ -325,6 +339,14 @@ void EmbeddedProcess::_check_focused_process_id() {
325339
focused_process_id = process_id;
326340
if (focused_process_id == current_process_id) {
327341
// The embedded process got the focus.
342+
343+
// Refocus the current model when focusing the embedded process.
344+
Window *modal_window = _get_current_modal_window();
345+
if (modal_window) {
346+
callable_mp(modal_window, &Window::grab_focus).call_deferred();
347+
return;
348+
}
349+
328350
emit_signal(SNAME("embedded_process_focused"));
329351
if (has_focus()) {
330352
// Redraw to updated the focus style.
@@ -336,6 +358,29 @@ void EmbeddedProcess::_check_focused_process_id() {
336358
release_focus();
337359
}
338360
}
361+
362+
// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
363+
if (!application_has_focus && focused_process_id == current_process_id) {
364+
Window *modal_window = _get_current_modal_window();
365+
if (modal_window) {
366+
callable_mp(modal_window, &Window::grab_focus).call_deferred();
367+
}
368+
}
369+
}
370+
371+
Window *EmbeddedProcess::_get_current_modal_window() {
372+
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
373+
for (const DisplayServer::WindowID &window_id : wl) {
374+
Window *w = Window::get_from_id(window_id);
375+
if (!w) {
376+
continue;
377+
}
378+
379+
if (w->is_exclusive()) {
380+
return w;
381+
}
382+
}
383+
return nullptr;
339384
}
340385

341386
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)