From 77d18d1ad4c250c1cfc5d946e259cc3ced0cc158 Mon Sep 17 00:00:00 2001 From: kobewi Date: Fri, 29 Nov 2024 21:16:53 +0100 Subject: [PATCH] Fix progress dialog steals focus Co-authored-by: Hilderin <81109165+Hilderin@users.noreply.github.com> --- editor/editor_node.cpp | 15 ++++++- editor/editor_node.h | 1 + editor/progress_dialog.cpp | 89 ++++++++++++++++++++++++++------------ editor/progress_dialog.h | 15 +++++-- editor/window_wrapper.cpp | 7 +++ editor/window_wrapper.h | 2 + 6 files changed, 98 insertions(+), 31 deletions(-) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 7f46bfb4201..f6abe248877 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -340,6 +340,19 @@ void EditorNode::_update_title() { } } +void EditorNode::input(const Ref &p_event) { + // EditorNode::get_singleton()->set_process_input is set to true in ProgressDialog + // only when the progress dialog is visible. + // We need to discard all key events to disable all shortcuts while the progress + // dialog is displayed, simulating an exclusive popup. Mouse events are + // captured by a full-screen container in front of the EditorNode in ProgressDialog, + // allowing interaction with the actual dialog where a Cancel button may be visible. + Ref k = p_event; + if (k.is_valid()) { + get_tree()->get_root()->set_input_as_handled(); + } +} + void EditorNode::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -7052,7 +7065,7 @@ EditorNode::EditorNode() { resource_preview = memnew(EditorResourcePreview); add_child(resource_preview); progress_dialog = memnew(ProgressDialog); - progress_dialog->set_unparent_when_invisible(true); + add_child(progress_dialog); progress_dialog->connect(SceneStringName(visibility_changed), callable_mp(this, &EditorNode::_progress_dialog_visibility_changed)); gui_base = memnew(Panel); diff --git a/editor/editor_node.h b/editor/editor_node.h index a3b3f0620b1..b7e166db704 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -591,6 +591,7 @@ private: void _exit_editor(int p_exit_code); + virtual void input(const Ref &p_event) override; virtual void shortcut_input(const Ref &p_event) override; bool has_main_screen() const { return true; } diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp index 2f345e51616..ae06116797f 100644 --- a/editor/progress_dialog.cpp +++ b/editor/progress_dialog.cpp @@ -35,6 +35,8 @@ #include "editor/editor_node.h" #include "editor/themes/editor_scale.h" #include "main/main.h" +#include "scene/gui/panel_container.h" +#include "scene/main/window.h" #include "servers/display_server.h" void BackgroundProgress::_add_task(const String &p_task, const String &p_label, int p_steps) { @@ -126,6 +128,21 @@ void BackgroundProgress::end_task(const String &p_task) { ProgressDialog *ProgressDialog::singleton = nullptr; +void ProgressDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + Ref style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu")); + main_border_size = style->get_minimum_size(); + main->set_offset(SIDE_LEFT, style->get_margin(SIDE_LEFT)); + main->set_offset(SIDE_RIGHT, -style->get_margin(SIDE_RIGHT)); + main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP)); + main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM)); + + center_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), "PopupPanel")); + } break; + } +} + void ProgressDialog::_update_ui() { // Run main loop for two frames. if (is_inside_tree()) { @@ -135,33 +152,33 @@ void ProgressDialog::_update_ui() { } void ProgressDialog::_popup() { + // Activate processing of all inputs in EditorNode, and the EditorNode::input method + // will discard every key input. + EditorNode::get_singleton()->set_process_input(true); + // Disable all other windows to prevent interaction with them. + for (Window *w : host_windows) { + w->set_process_mode(PROCESS_MODE_DISABLED); + } + Size2 ms = main->get_combined_minimum_size(); ms.width = MAX(500 * EDSCALE, ms.width); + ms += main_border_size; - Ref style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu")); - ms += style->get_minimum_size(); + center_panel->set_custom_minimum_size(ms); - main->set_offset(SIDE_LEFT, style->get_margin(SIDE_LEFT)); - main->set_offset(SIDE_RIGHT, -style->get_margin(SIDE_RIGHT)); - main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP)); - main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM)); - - if (is_inside_tree()) { - Rect2i adjust = _popup_adjust_rect(); - if (adjust != Rect2i()) { - set_position(adjust.position); - set_size(adjust.size); - } - } else { - for (Window *window : host_windows) { - if (window->has_focus()) { - popup_exclusive_centered(window, ms); - return; - } - } - // No host window found, use main window. - EditorInterface::get_singleton()->popup_dialog_centered(this, ms); + Window *current_window = Window::get_from_id(DisplayServer::get_singleton()->get_focused_window()); + if (!current_window) { + current_window = get_tree()->get_root(); } + + reparent(current_window); + + // Ensures that events are properly released before the dialog blocks input. + bool window_is_input_disabled = current_window->is_input_disabled(); + current_window->set_disable_input(!window_is_input_disabled); + current_window->set_disable_input(window_is_input_disabled); + + show(); } void ProgressDialog::add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) { @@ -231,6 +248,10 @@ void ProgressDialog::end_task(const String &p_task) { if (tasks.is_empty()) { hide(); + EditorNode::get_singleton()->set_process_input(false); + for (Window *w : host_windows) { + w->set_process_mode(PROCESS_MODE_INHERIT); + } } else { _popup(); } @@ -241,17 +262,31 @@ void ProgressDialog::add_host_window(Window *p_window) { host_windows.push_back(p_window); } +void ProgressDialog::remove_host_window(Window *p_window) { + ERR_FAIL_NULL(p_window); + host_windows.erase(p_window); +} + void ProgressDialog::_cancel_pressed() { canceled = true; } ProgressDialog::ProgressDialog() { - main = memnew(VBoxContainer); - add_child(main); - main->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - set_exclusive(true); - set_flag(Window::FLAG_POPUP, false); + // We want to cover the entire screen to prevent the user from interacting with the Editor. + set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + // Be sure it's the top most component. + set_z_index(RS::CANVAS_ITEM_Z_MAX); singleton = this; + hide(); + + center_panel = memnew(PanelContainer); + add_child(center_panel); + center_panel->set_h_size_flags(SIZE_SHRINK_BEGIN); + center_panel->set_v_size_flags(SIZE_SHRINK_BEGIN); + + main = memnew(VBoxContainer); + center_panel->add_child(main); + cancel_hb = memnew(HBoxContainer); main->add_child(cancel_hb); cancel_hb->hide(); diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h index 355812b0b7a..87fb02b37f9 100644 --- a/editor/progress_dialog.h +++ b/editor/progress_dialog.h @@ -33,8 +33,8 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/center_container.h" #include "scene/gui/label.h" -#include "scene/gui/popup.h" #include "scene/gui/progress_bar.h" class BackgroundProgress : public HBoxContainer { @@ -64,8 +64,10 @@ public: BackgroundProgress() {} }; -class ProgressDialog : public PopupPanel { - GDCLASS(ProgressDialog, PopupPanel); +class PanelContainer; + +class ProgressDialog : public CenterContainer { + GDCLASS(ProgressDialog, CenterContainer); struct Task { String task; VBoxContainer *vb = nullptr; @@ -77,10 +79,13 @@ class ProgressDialog : public PopupPanel { Button *cancel = nullptr; HashMap tasks; + PanelContainer *center_panel = nullptr; VBoxContainer *main = nullptr; LocalVector host_windows; + Size2 main_border_size; + static ProgressDialog *singleton; void _popup(); @@ -89,6 +94,9 @@ class ProgressDialog : public PopupPanel { void _update_ui(); bool canceled = false; +protected: + void _notification(int p_what); + public: static ProgressDialog *get_singleton() { return singleton; } void add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel = false); @@ -96,6 +104,7 @@ public: void end_task(const String &p_task); void add_host_window(Window *p_window); + void remove_host_window(Window *p_window); ProgressDialog(); }; diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index 97ecda9bc93..37a3efc91e9 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -336,6 +336,7 @@ WindowWrapper::WindowWrapper() { } window = memnew(Window); + window_id = window->get_instance_id(); window->set_wrap_controls(true); add_child(window); @@ -354,6 +355,12 @@ WindowWrapper::WindowWrapper() { ProgressDialog::get_singleton()->add_host_window(window); } +WindowWrapper::~WindowWrapper() { + if (ObjectDB::get_instance(window_id)) { + ProgressDialog::get_singleton()->remove_host_window(window); + } +} + // ScreenSelect void ScreenSelect::_build_advanced_menu() { diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h index 0a9f0f21d1b..3f4e9385861 100644 --- a/editor/window_wrapper.h +++ b/editor/window_wrapper.h @@ -44,6 +44,7 @@ class WindowWrapper : public MarginContainer { Control *wrapped_control = nullptr; MarginContainer *margins = nullptr; Window *window = nullptr; + ObjectID window_id; Panel *window_background = nullptr; @@ -84,6 +85,7 @@ public: void grab_window_focus(); WindowWrapper(); + ~WindowWrapper(); }; class ScreenSelect : public Button {