Pārlūkot izejas kodu

Merge pull request #95044 from Hilderin/fix-double-click-signal-connection

Fix connecting a signal with a double click is too difficult
Rémi Verschelde 10 mēneši atpakaļ
vecāks
revīzija
20064cf5c0

+ 20 - 0
core/input/input.cpp

@@ -308,6 +308,26 @@ bool Input::is_anything_pressed() const {
 	return false;
 }
 
+bool Input::is_anything_pressed_except_mouse() const {
+	_THREAD_SAFE_METHOD_
+
+	if (disable_input) {
+		return false;
+	}
+
+	if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty()) {
+		return true;
+	}
+
+	for (const KeyValue<StringName, Input::ActionState> &E : action_states) {
+		if (E.value.cache.pressed) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
 bool Input::is_key_pressed(Key p_keycode) const {
 	_THREAD_SAFE_METHOD_
 

+ 1 - 0
core/input/input.h

@@ -294,6 +294,7 @@ public:
 	static Input *get_singleton();
 
 	bool is_anything_pressed() const;
+	bool is_anything_pressed_except_mouse() const;
 	bool is_key_pressed(Key p_keycode) const;
 	bool is_physical_key_pressed(Key p_keycode) const;
 	bool is_key_label_pressed(Key p_keycode) const;

+ 1 - 3
editor/connections_dialog.cpp

@@ -911,9 +911,7 @@ Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
 		return nullptr;
 	}
 
-	EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text));
-	EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ConnectionsDockTree *>(this));
-	return memnew(Control); // Make the standard tooltip invisible.
+	return EditorHelpBitTooltip::show_tooltip(const_cast<ConnectionsDockTree *>(this), p_text);
 }
 
 struct _ConnectionsDockMethodInfoSort {

+ 86 - 49
editor/editor_help.cpp

@@ -3690,7 +3690,7 @@ void EditorHelpBit::_notification(int p_what) {
 	}
 }
 
-void EditorHelpBit::parse_symbol(const String &p_symbol) {
+void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologue) {
 	const PackedStringArray slices = p_symbol.split("|", true, 2);
 	ERR_FAIL_COND_MSG(slices.size() < 3, "Invalid doc id. The expected format is 'item_type|class_name|item_name'.");
 
@@ -3737,6 +3737,14 @@ void EditorHelpBit::parse_symbol(const String &p_symbol) {
 	symbol_visible_type = visible_type;
 	symbol_name = name;
 
+	if (!p_prologue.is_empty()) {
+		if (help_data.description.is_empty()) {
+			help_data.description = p_prologue;
+		} else {
+			help_data.description = p_prologue + "\n" + help_data.description;
+		}
+	}
+
 	if (help_data.description.is_empty()) {
 		help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
 	}
@@ -3760,14 +3768,6 @@ void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name,
 	}
 }
 
-void EditorHelpBit::set_description(const String &p_text) {
-	help_data.description = p_text;
-
-	if (is_inside_tree()) {
-		_update_labels();
-	}
-}
-
 void EditorHelpBit::set_content_height_limits(float p_min, float p_max) {
 	ERR_FAIL_COND(p_min > p_max);
 	content_min_height = p_min;
@@ -3787,15 +3787,15 @@ void EditorHelpBit::update_content_height() {
 	content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height)));
 }
 
-EditorHelpBit::EditorHelpBit(const String &p_symbol) {
+EditorHelpBit::EditorHelpBit(const String &p_symbol, const String &p_prologue, bool p_allow_selection) {
 	add_theme_constant_override("separation", 0);
 
 	title = memnew(RichTextLabel);
 	title->set_theme_type_variation("EditorHelpBitTitle");
 	title->set_custom_minimum_size(Size2(512 * EDSCALE, 0)); // GH-93031. Set the minimum width even if `fit_content` is true.
 	title->set_fit_content(true);
-	title->set_selection_enabled(true);
-	//title->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip.
+	title->set_selection_enabled(p_allow_selection);
+	title->set_context_menu_enabled(p_allow_selection);
 	title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
 	title->hide();
 	add_child(title);
@@ -3806,76 +3806,108 @@ EditorHelpBit::EditorHelpBit(const String &p_symbol) {
 	content = memnew(RichTextLabel);
 	content->set_theme_type_variation("EditorHelpBitContent");
 	content->set_custom_minimum_size(Size2(512 * EDSCALE, content_min_height));
-	content->set_selection_enabled(true);
-	//content->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip.
+	content->set_selection_enabled(p_allow_selection);
+	content->set_context_menu_enabled(p_allow_selection);
 	content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
 	add_child(content);
 
 	if (!p_symbol.is_empty()) {
-		parse_symbol(p_symbol);
+		parse_symbol(p_symbol, p_prologue);
 	}
 }
 
 /// EditorHelpBitTooltip ///
 
+bool EditorHelpBitTooltip::_is_tooltip_visible = false;
+
+Control *EditorHelpBitTooltip::_make_invisible_control() {
+	Control *control = memnew(Control);
+	control->set_visible(false);
+	return control;
+}
+
 void EditorHelpBitTooltip::_start_timer() {
 	if (timer->is_inside_tree() && timer->is_stopped()) {
 		timer->start();
 	}
 }
 
-void EditorHelpBitTooltip::_safe_queue_free() {
-	if (_pushing_input > 0) {
-		_need_free = true;
-	} else {
-		queue_free();
-	}
-}
-
 void EditorHelpBitTooltip::_target_gui_input(const Ref<InputEvent> &p_event) {
-	const Ref<InputEventMouse> mouse_event = p_event;
-	if (mouse_event.is_valid()) {
-		_start_timer();
+	// Only scrolling is not checked in `NOTIFICATION_INTERNAL_PROCESS`.
+	const Ref<InputEventMouseButton> mb = p_event;
+	if (mb.is_valid()) {
+		switch (mb->get_button_index()) {
+			case MouseButton::WHEEL_UP:
+			case MouseButton::WHEEL_DOWN:
+			case MouseButton::WHEEL_LEFT:
+			case MouseButton::WHEEL_RIGHT:
+				queue_free();
+				break;
+			default:
+				break;
+		}
 	}
 }
 
 void EditorHelpBitTooltip::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE:
+			_is_tooltip_visible = true;
+			_enter_tree_time = OS::get_singleton()->get_ticks_msec();
+			break;
+		case NOTIFICATION_EXIT_TREE:
+			_is_tooltip_visible = false;
+			break;
 		case NOTIFICATION_WM_MOUSE_ENTER:
+			_is_mouse_inside_tooltip = true;
 			timer->stop();
 			break;
 		case NOTIFICATION_WM_MOUSE_EXIT:
+			_is_mouse_inside_tooltip = false;
 			_start_timer();
 			break;
+		case NOTIFICATION_INTERNAL_PROCESS:
+			// A workaround to hide the tooltip since the window does not receive keyboard events
+			// with `FLAG_POPUP` and `FLAG_NO_FOCUS` flags, so we can't use `_input_from_window()`.
+			if (is_inside_tree()) {
+				if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"), true)) {
+					queue_free();
+					get_parent_viewport()->set_input_as_handled();
+				} else if (Input::get_singleton()->is_anything_pressed_except_mouse()) {
+					queue_free();
+				} else if (!Input::get_singleton()->get_mouse_button_mask().is_empty()) {
+					if (!_is_mouse_inside_tooltip) {
+						queue_free();
+					}
+				} else if (!Input::get_singleton()->get_last_mouse_velocity().is_zero_approx()) {
+					if (!_is_mouse_inside_tooltip && OS::get_singleton()->get_ticks_msec() - _enter_tree_time > 250) {
+						_start_timer();
+					}
+				}
+			}
+			break;
 	}
 }
 
-// Forwards non-mouse input to the parent viewport.
-void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) {
-	if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
-		_safe_queue_free();
-	} else {
-		const Ref<InputEventMouse> mouse_event = p_event;
-		if (mouse_event.is_null()) {
-			// GH-91652. Prevents use-after-free since `ProgressDialog` calls `Main::iteration()`.
-			_pushing_input++;
-			get_parent_viewport()->push_input(p_event);
-			_pushing_input--;
-			if (_pushing_input <= 0 && _need_free) {
-				queue_free();
-			}
-		}
+Control *EditorHelpBitTooltip::show_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue) {
+	// Show the custom tooltip only if it is not already visible.
+	// The viewport will retrigger `make_custom_tooltip()` every few seconds
+	// because the return control is not visible even if the custom tooltip is displayed.
+	if (_is_tooltip_visible || Input::get_singleton()->is_anything_pressed()) {
+		return _make_invisible_control();
 	}
-}
 
-void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) {
-	ERR_FAIL_NULL(p_help_bit);
+	EditorHelpBit *help_bit = memnew(EditorHelpBit(p_symbol, p_prologue, false));
+
 	EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target));
-	p_help_bit->connect("request_hide", callable_mp(tooltip, &EditorHelpBitTooltip::_safe_queue_free));
-	tooltip->add_child(p_help_bit);
+	help_bit->connect("request_hide", callable_mp(static_cast<Node *>(tooltip), &Node::queue_free));
+	tooltip->add_child(help_bit);
 	p_target->add_child(tooltip);
-	p_help_bit->update_content_height();
+
+	help_bit->update_content_height();
 	tooltip->popup_under_cursor();
+
+	return _make_invisible_control();
 }
 
 // Copy-paste from `Viewport::_gui_show_tooltip()`.
@@ -3915,6 +3947,9 @@ void EditorHelpBitTooltip::popup_under_cursor() {
 		r.position.y = vr.position.y;
 	}
 
+	// When `FLAG_POPUP` is false, it prevents the editor from losing focus when displaying the tooltip.
+	// This way, clicks and double-clicks are still available outside the tooltip.
+	set_flag(Window::FLAG_POPUP, false);
 	set_flag(Window::FLAG_NO_FOCUS, true);
 	popup(r);
 }
@@ -3923,13 +3958,15 @@ EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) {
 	set_theme_type_variation("TooltipPanel");
 
 	timer = memnew(Timer);
-	timer->set_wait_time(0.2);
-	timer->connect("timeout", callable_mp(this, &EditorHelpBitTooltip::_safe_queue_free));
+	timer->set_wait_time(0.25);
+	timer->connect("timeout", callable_mp(static_cast<Node *>(this), &Node::queue_free));
 	add_child(timer);
 
 	ERR_FAIL_NULL(p_target);
 	p_target->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorHelpBitTooltip::_start_timer));
 	p_target->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelpBitTooltip::_target_gui_input));
+
+	set_process_internal(true);
 }
 
 #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)

+ 9 - 9
editor/editor_help.h

@@ -309,15 +309,13 @@ protected:
 	void _notification(int p_what);
 
 public:
-	void parse_symbol(const String &p_symbol);
+	void parse_symbol(const String &p_symbol, const String &p_prologue = String());
 	void set_custom_text(const String &p_type, const String &p_name, const String &p_description);
-	void set_description(const String &p_text);
-	_FORCE_INLINE_ String get_description() const { return help_data.description; }
 
 	void set_content_height_limits(float p_min, float p_max);
 	void update_content_height();
 
-	EditorHelpBit(const String &p_symbol = String());
+	EditorHelpBit(const String &p_symbol = String(), const String &p_prologue = String(), bool p_allow_selection = true);
 };
 
 // Standard tooltips do not allow you to hover over them.
@@ -325,20 +323,22 @@ public:
 class EditorHelpBitTooltip : public PopupPanel {
 	GDCLASS(EditorHelpBitTooltip, PopupPanel);
 
+	static bool _is_tooltip_visible;
+
 	Timer *timer = nullptr;
-	int _pushing_input = 0;
-	bool _need_free = false;
+	uint64_t _enter_tree_time = 0;
+	bool _is_mouse_inside_tooltip = false;
+
+	static Control *_make_invisible_control();
 
 	void _start_timer();
-	void _safe_queue_free();
 	void _target_gui_input(const Ref<InputEvent> &p_event);
 
 protected:
 	void _notification(int p_what);
-	virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
 
 public:
-	static void show_tooltip(EditorHelpBit *p_help_bit, Control *p_target);
+	static Control *show_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue = String());
 
 	void popup_under_cursor();
 

+ 20 - 25
editor/editor_inspector.cpp

@@ -1005,36 +1005,33 @@ void EditorProperty::_update_flags() {
 }
 
 Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
-	String custom_warning;
+	String symbol;
+	String prologue;
+
 	if (object->has_method("_get_property_warning")) {
-		custom_warning = object->call("_get_property_warning", property);
+		const String custom_warning = object->call("_get_property_warning", property);
+		if (!custom_warning.is_empty()) {
+			prologue = "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]";
+		}
 	}
 
-	if (has_doc_tooltip || !custom_warning.is_empty()) {
-		EditorHelpBit *help_bit = memnew(EditorHelpBit);
+	if (has_doc_tooltip) {
+		symbol = p_text;
 
-		if (has_doc_tooltip) {
-			help_bit->parse_symbol(p_text);
-
-			const EditorInspector *inspector = get_parent_inspector();
-			if (inspector) {
-				const String custom_description = inspector->get_custom_property_description(p_text);
-				if (!custom_description.is_empty()) {
-					help_bit->set_description(custom_description);
+		const EditorInspector *inspector = get_parent_inspector();
+		if (inspector) {
+			const String custom_description = inspector->get_custom_property_description(p_text);
+			if (!custom_description.is_empty()) {
+				if (!prologue.is_empty()) {
+					prologue += '\n';
 				}
+				prologue += custom_description;
 			}
 		}
+	}
 
-		if (!custom_warning.is_empty()) {
-			String description = "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]";
-			if (!help_bit->get_description().is_empty()) {
-				description += "\n" + help_bit->get_description();
-			}
-			help_bit->set_description(description);
-		}
-
-		EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorProperty *>(this));
-		return memnew(Control); // Make the standard tooltip invisible.
+	if (!symbol.is_empty() || !prologue.is_empty()) {
+		return EditorHelpBitTooltip::show_tooltip(const_cast<EditorProperty *>(this), symbol, prologue);
 	}
 
 	return nullptr;
@@ -1331,9 +1328,7 @@ Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) cons
 		return nullptr;
 	}
 
-	EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text));
-	EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorInspectorCategory *>(this));
-	return memnew(Control); // Make the standard tooltip invisible.
+	return EditorHelpBitTooltip::show_tooltip(const_cast<EditorInspectorCategory *>(this), p_text);
 }
 
 void EditorInspectorCategory::set_as_favorite(EditorInspector *p_for_inspector) {

+ 1 - 3
editor/plugins/theme_editor_plugin.cpp

@@ -2284,9 +2284,7 @@ ThemeTypeDialog::ThemeTypeDialog() {
 ///////////////////////
 
 Control *ThemeItemLabel::make_custom_tooltip(const String &p_text) const {
-	EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text));
-	EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ThemeItemLabel *>(this));
-	return memnew(Control); // Make the standard tooltip invisible.
+	return EditorHelpBitTooltip::show_tooltip(const_cast<ThemeItemLabel *>(this), p_text);
 }
 
 VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) {

+ 1 - 8
platform/linuxbsd/x11/display_server_x11.cpp

@@ -4857,14 +4857,7 @@ void DisplayServerX11::process_events() {
 
 					WindowID window_id_other = INVALID_WINDOW_ID;
 					Window wd_other_x11_window;
-					if (wd.focused) {
-						// Handle cases where an unfocused popup is open that needs to receive button-up events.
-						WindowID popup_id = _get_focused_window_or_popup();
-						if (popup_id != INVALID_WINDOW_ID && popup_id != window_id) {
-							window_id_other = popup_id;
-							wd_other_x11_window = windows[popup_id].x11_window;
-						}
-					} else {
+					if (!wd.focused) {
 						// Propagate the event to the focused window,
 						// because it's received only on the topmost window.
 						// Note: This is needed for drag & drop to work between windows,

+ 8 - 1
scene/main/viewport.cpp

@@ -1455,7 +1455,14 @@ void Viewport::_gui_show_tooltip() {
 
 	// Controls can implement `make_custom_tooltip` to provide their own tooltip.
 	// This should be a Control node which will be added as child to a TooltipPanel.
-	Control *base_tooltip = tooltip_owner ? tooltip_owner->make_custom_tooltip(gui.tooltip_text) : nullptr;
+	Control *base_tooltip = tooltip_owner->make_custom_tooltip(gui.tooltip_text);
+
+	// When the custom control is not visible, don't show any tooltip.
+	// This way, the custom tooltip from `ConnectionsDockTree` can create
+	// its own tooltip without conflicting with the default one, even an empty tooltip.
+	if (base_tooltip && !base_tooltip->is_visible()) {
+		return;
+	}
 
 	if (gui.tooltip_text.is_empty() && !base_tooltip) {
 		return; // Nothing to show.