mirror of
https://github.com/godotengine/godot.git
synced 2025-04-07 00:44:24 +08:00
Merge pull request #95044 from Hilderin/fix-double-click-signal-connection
Fix connecting a signal with a double click is too difficult
This commit is contained in:
commit
20064cf5c0
@ -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_
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user