Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix connecting a signal with a double click is too difficult #95044

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions core/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_

Expand Down
1 change: 1 addition & 0 deletions core/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ class Input : public Object {
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;
Expand Down
4 changes: 1 addition & 3 deletions editor/connections_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
135 changes: 86 additions & 49 deletions editor/editor_help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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'.");

Expand Down Expand Up @@ -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]";
}
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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);
}
Comment on lines 3814 to 3816
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite my best efforts, I still missed one thing. Please add this if you will cherry-pick this for 4.3. For master, this will be fixed in #91060.

 	if (!p_symbol.is_empty()) {
 		parse_symbol(p_symbol, p_prologue);
+	} else if (!p_prologue.is_empty()) {
+		set_custom_text(String(), String(), 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()`.
Expand Down Expand Up @@ -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);
}
Expand All @@ -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)
Expand Down
18 changes: 9 additions & 9 deletions editor/editor_help.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,36 +309,36 @@ class EditorHelpBit : public VBoxContainer {
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.
// This class is intended as a temporary workaround.
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();

Expand Down
45 changes: 20 additions & 25 deletions editor/editor_inspector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 1 addition & 3 deletions editor/plugins/theme_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading
Loading