Browse Source

Merge pull request #99844 from KoBeWi/give_back_the_focus

Fix progress dialog steals focus
Rémi Verschelde 7 months ago
parent
commit
8cf6061dfd

+ 14 - 1
editor/editor_node.cpp

@@ -341,6 +341,19 @@ void EditorNode::_update_title() {
 	}
 }
 
+void EditorNode::input(const Ref<InputEvent> &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<InputEventKey> k = p_event;
+	if (k.is_valid()) {
+		get_tree()->get_root()->set_input_as_handled();
+	}
+}
+
 void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
 	ERR_FAIL_COND(p_event.is_null());
 
@@ -7079,7 +7092,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);

+ 1 - 0
editor/editor_node.h

@@ -592,6 +592,7 @@ private:
 
 	void _exit_editor(int p_exit_code);
 
+	virtual void input(const Ref<InputEvent> &p_event) override;
 	virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
 
 	bool has_main_screen() const { return true; }

+ 62 - 27
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<StyleBox> 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<StyleBox> style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu"));
-	ms += 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->set_custom_minimum_size(ms);
 
-	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();

+ 12 - 3
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<String, Task> tasks;
+	PanelContainer *center_panel = nullptr;
 	VBoxContainer *main = nullptr;
 
 	LocalVector<Window *> 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();
 };

+ 7 - 0
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() {

+ 2 - 0
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 {