Unfold tree items on hover while drag-n-dropping

Co-authored-by: suddjian
This commit is contained in:
Michael Alexsander 2025-09-24 22:34:10 -03:00
parent 1f7630f1bf
commit 26745b4b87
No known key found for this signature in database
GPG key ID: A9C91EE110F4EABA
11 changed files with 82 additions and 4 deletions

View file

@ -869,6 +869,9 @@
<member name="interface/editor/dock_tab_style" type="int" setter="" getter=""> <member name="interface/editor/dock_tab_style" type="int" setter="" getter="">
Tab style of editor docks. Tab style of editor docks.
</member> </member>
<member name="interface/editor/dragging_hover_wait_seconds" type="float" setter="" getter="">
During a drag-and-drop, this is how long to wait over a UI element before it triggers a reaction (e.g. a section unfolds to show nested items).
</member>
<member name="interface/editor/editor_language" type="String" setter="" getter=""> <member name="interface/editor/editor_language" type="String" setter="" getter="">
The language to use for the editor interface. The language to use for the editor interface.
Translations are provided by the community. If you spot a mistake, [url=https://contributing.godotengine.org/en/latest/documentation/translation/index.html]contribute to editor translations on Weblate![/url] Translations are provided by the community. If you spot a mistake, [url=https://contributing.godotengine.org/en/latest/documentation/translation/index.html]contribute to editor translations on Weblate![/url]

View file

@ -355,6 +355,9 @@
The drop mode as an OR combination of flags. See [enum DropModeFlags] constants. Once dropping is done, reverts to [constant DROP_MODE_DISABLED]. Setting this during [method Control._can_drop_data] is recommended. The drop mode as an OR combination of flags. See [enum DropModeFlags] constants. Once dropping is done, reverts to [constant DROP_MODE_DISABLED]. Setting this during [method Control._can_drop_data] is recommended.
This controls the drop sections, i.e. the decision and drawing of possible drop locations based on the mouse position. This controls the drop sections, i.e. the decision and drawing of possible drop locations based on the mouse position.
</member> </member>
<member name="enable_drag_unfolding" type="bool" setter="set_enable_drag_unfolding" getter="is_drag_unfolding_enabled" default="true">
If [code]true[/code], tree items will unfold when hovered over during a drag-and-drop. The delay for when this happens is dictated by [theme_item dragging_unfold_wait_msec].
</member>
<member name="enable_recursive_folding" type="bool" setter="set_enable_recursive_folding" getter="is_recursive_folding_enabled" default="true"> <member name="enable_recursive_folding" type="bool" setter="set_enable_recursive_folding" getter="is_recursive_folding_enabled" default="true">
If [code]true[/code], recursive folding is enabled for this [Tree]. Holding down [kbd]Shift[/kbd] while clicking the fold arrow or using [code]ui_right[/code]/[code]ui_left[/code] shortcuts collapses or uncollapses the [TreeItem] and all its descendants. If [code]true[/code], recursive folding is enabled for this [Tree]. Holding down [kbd]Shift[/kbd] while clicking the fold arrow or using [code]ui_right[/code]/[code]ui_left[/code] shortcuts collapses or uncollapses the [TreeItem] and all its descendants.
</member> </member>
@ -546,6 +549,9 @@
<theme_item name="children_hl_line_width" data_type="constant" type="int" default="1"> <theme_item name="children_hl_line_width" data_type="constant" type="int" default="1">
The width of the relationship lines between the selected [TreeItem] and its children. The width of the relationship lines between the selected [TreeItem] and its children.
</theme_item> </theme_item>
<theme_item name="dragging_unfold_wait_msec" data_type="constant" type="int" default="500">
During a drag-and-drop, this is how many milliseconds to wait over a section before the section unfolds.
</theme_item>
<theme_item name="draw_guides" data_type="constant" type="int" default="1"> <theme_item name="draw_guides" data_type="constant" type="int" default="1">
Draws the guidelines if not zero, this acts as a boolean. The guideline is a horizontal line drawn at the bottom of each item. Draws the guidelines if not zero, this acts as a boolean. The guideline is a horizontal line drawn at the bottom of each item.
</theme_item> </theme_item>

View file

@ -1711,6 +1711,9 @@ void SceneTreeDock::_notification(int p_what) {
scene_tree->set_hide_filtered_out_parents(EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"), false); scene_tree->set_hide_filtered_out_parents(EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"), false);
scene_tree->set_accessibility_warnings(EDITOR_GET("docks/scene_tree/accessibility_warnings"), false); scene_tree->set_accessibility_warnings(EDITOR_GET("docks/scene_tree/accessibility_warnings"), false);
} }
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) {
inspect_hovered_node_delay->set_wait_time(EDITOR_GET("interface/editor/dragging_hover_wait_seconds"));
}
} break; } break;
case NOTIFICATION_THEME_CHANGED: { case NOTIFICATION_THEME_CHANGED: {
@ -4854,7 +4857,6 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
inspect_hovered_node_delay = memnew(Timer); inspect_hovered_node_delay = memnew(Timer);
inspect_hovered_node_delay->connect("timeout", callable_mp(this, &SceneTreeDock::_inspect_hovered_node)); inspect_hovered_node_delay->connect("timeout", callable_mp(this, &SceneTreeDock::_inspect_hovered_node));
inspect_hovered_node_delay->set_wait_time(.5);
inspect_hovered_node_delay->set_one_shot(true); inspect_hovered_node_delay->set_one_shot(true);
add_child(inspect_hovered_node_delay); add_child(inspect_hovered_node_delay);

View file

@ -995,6 +995,10 @@ void EditorNode::_notification(int p_what) {
recent_scenes->reset_size(); recent_scenes->reset_size();
} }
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) {
theme->set_constant("dragging_unfold_wait_msec", "Tree", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000);
}
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/dock_tab_style")) { if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/dock_tab_style")) {
editor_dock_manager->update_tab_styles(); editor_dock_manager->update_tab_styles();
} }

View file

@ -2138,6 +2138,12 @@ void EditorInspectorSection::_notification(int p_what) {
queue_redraw(); queue_redraw();
} }
} break; } break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) {
dropping_unfold_timer->set_wait_time(EDITOR_GET("interface/editor/dragging_hover_wait_seconds"));
}
} break;
} }
} }
@ -2466,7 +2472,7 @@ EditorInspectorSection::EditorInspectorSection() {
vbox = memnew(VBoxContainer); vbox = memnew(VBoxContainer);
dropping_unfold_timer = memnew(Timer); dropping_unfold_timer = memnew(Timer);
dropping_unfold_timer->set_wait_time(0.6); dropping_unfold_timer->set_wait_time(EDITOR_GET("interface/editor/dragging_hover_wait_seconds"));
dropping_unfold_timer->set_one_shot(true); dropping_unfold_timer->set_one_shot(true);
add_child(dropping_unfold_timer); add_child(dropping_unfold_timer);
dropping_unfold_timer->connect("timeout", callable_mp(this, &EditorInspectorSection::unfold)); dropping_unfold_timer->connect("timeout", callable_mp(this, &EditorInspectorSection::unfold));

View file

@ -1362,6 +1362,8 @@ void SceneTreeEditor::_notification(int p_what) {
} break; } break;
case NOTIFICATION_THEME_CHANGED: { case NOTIFICATION_THEME_CHANGED: {
// Wait for the node to be inspected before triggering the unfolding.
tree->add_theme_constant_override("dragging_unfold_wait_msec", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000 * 2);
tree->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor))); tree->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
[[fallthrough]]; [[fallthrough]];
} }

View file

@ -503,6 +503,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm") EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm")
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font_bold", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm") EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font_bold", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm")
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/code_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm") EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/code_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm")
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/dragging_hover_wait_seconds", 0.5, "0.01,10,0.01,or_greater,suffix:s");
_initial_set("interface/editor/separate_distraction_mode", false, true); _initial_set("interface/editor/separate_distraction_mode", false, true);
_initial_set("interface/editor/automatically_open_screenshots", true, true); _initial_set("interface/editor/automatically_open_screenshots", true, true);
EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING) EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)

View file

@ -975,6 +975,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_constant("inner_item_margin_left", "Tree", p_config.increased_margin * EDSCALE); p_theme->set_constant("inner_item_margin_left", "Tree", p_config.increased_margin * EDSCALE);
p_theme->set_constant("inner_item_margin_right", "Tree", p_config.increased_margin * EDSCALE); p_theme->set_constant("inner_item_margin_right", "Tree", p_config.increased_margin * EDSCALE);
p_theme->set_constant("button_margin", "Tree", p_config.base_margin * EDSCALE); p_theme->set_constant("button_margin", "Tree", p_config.base_margin * EDSCALE);
p_theme->set_constant("dragging_unfold_wait_msec", "Tree", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000);
p_theme->set_constant("scroll_border", "Tree", 40 * EDSCALE); p_theme->set_constant("scroll_border", "Tree", 40 * EDSCALE);
p_theme->set_constant("scroll_speed", "Tree", 12); p_theme->set_constant("scroll_speed", "Tree", 12);
p_theme->set_constant("outline_size", "Tree", 0); p_theme->set_constant("outline_size", "Tree", 0);

View file

@ -139,7 +139,7 @@ void TreeItem::_change_tree(Tree *p_tree) {
} }
if (tree->drop_mode_over == this) { if (tree->drop_mode_over == this) {
tree->drop_mode_over = nullptr; tree->_reset_drop_mode_over();
} }
if (tree->single_select_defer == this) { if (tree->single_select_defer == this) {
@ -4278,6 +4278,9 @@ void Tree::_determine_hovered_item() {
if (drop_mode_flags) { if (drop_mode_flags) {
if (it != drop_mode_over) { if (it != drop_mode_over) {
drop_mode_over = it; drop_mode_over = it;
if (enable_drag_unfolding) {
dropping_unfold_timer->start(theme_cache.dragging_unfold_wait_msec * 0.001);
}
queue_redraw(); queue_redraw();
} }
if (it && section != drop_mode_section) { if (it && section != drop_mode_section) {
@ -4321,6 +4324,19 @@ void Tree::_determine_hovered_item() {
} }
} }
void Tree::_on_dropping_unfold_timer_timeout() {
if (enable_drag_unfolding && drop_mode_over && drop_mode_section == 0) {
drop_mode_over->set_collapsed(false);
}
}
void Tree::_reset_drop_mode_over() {
drop_mode_over = nullptr;
if (!dropping_unfold_timer->is_stopped()) {
dropping_unfold_timer->stop();
}
}
void Tree::_queue_update_hovered_item() { void Tree::_queue_update_hovered_item() {
if (hovered_update_queued) { if (hovered_update_queued) {
return; return;
@ -6440,13 +6456,28 @@ bool Tree::is_recursive_folding_enabled() const {
return enable_recursive_folding; return enable_recursive_folding;
} }
void Tree::set_enable_drag_unfolding(bool p_enable) {
if (enable_drag_unfolding == p_enable) {
return;
}
enable_drag_unfolding = p_enable;
if (!enable_drag_unfolding && !dropping_unfold_timer->is_stopped()) {
dropping_unfold_timer->stop();
}
}
bool Tree::is_drag_unfolding_enabled() const {
return enable_drag_unfolding;
}
void Tree::set_drop_mode_flags(int p_flags) { void Tree::set_drop_mode_flags(int p_flags) {
if (drop_mode_flags == p_flags) { if (drop_mode_flags == p_flags) {
return; return;
} }
drop_mode_flags = p_flags; drop_mode_flags = p_flags;
if (drop_mode_flags == 0) { if (drop_mode_flags == 0) {
drop_mode_over = nullptr; _reset_drop_mode_over();
} }
queue_redraw(); queue_redraw();
@ -6567,6 +6598,9 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enable_recursive_folding", "enable"), &Tree::set_enable_recursive_folding); ClassDB::bind_method(D_METHOD("set_enable_recursive_folding", "enable"), &Tree::set_enable_recursive_folding);
ClassDB::bind_method(D_METHOD("is_recursive_folding_enabled"), &Tree::is_recursive_folding_enabled); ClassDB::bind_method(D_METHOD("is_recursive_folding_enabled"), &Tree::is_recursive_folding_enabled);
ClassDB::bind_method(D_METHOD("set_enable_drag_unfolding", "enable"), &Tree::set_enable_drag_unfolding);
ClassDB::bind_method(D_METHOD("is_drag_unfolding_enabled"), &Tree::is_drag_unfolding_enabled);
ClassDB::bind_method(D_METHOD("set_drop_mode_flags", "flags"), &Tree::set_drop_mode_flags); ClassDB::bind_method(D_METHOD("set_drop_mode_flags", "flags"), &Tree::set_drop_mode_flags);
ClassDB::bind_method(D_METHOD("get_drop_mode_flags"), &Tree::get_drop_mode_flags); ClassDB::bind_method(D_METHOD("get_drop_mode_flags"), &Tree::get_drop_mode_flags);
@ -6589,6 +6623,7 @@ void Tree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_recursive_folding"), "set_enable_recursive_folding", "is_recursive_folding_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_recursive_folding"), "set_enable_recursive_folding", "is_recursive_folding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_drag_unfolding"), "set_enable_drag_unfolding", "is_drag_unfolding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden");
ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode");
@ -6687,6 +6722,8 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, parent_hl_line_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, parent_hl_line_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, children_hl_line_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, children_hl_line_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, dragging_unfold_wait_msec);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_border); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_border);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_speed); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_speed);
@ -6744,6 +6781,11 @@ Tree::Tree() {
range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout)); range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout));
add_child(range_click_timer, false, INTERNAL_MODE_FRONT); add_child(range_click_timer, false, INTERNAL_MODE_FRONT);
dropping_unfold_timer = memnew(Timer);
dropping_unfold_timer->set_one_shot(true);
dropping_unfold_timer->connect("timeout", callable_mp(this, &Tree::_on_dropping_unfold_timer_timeout));
add_child(dropping_unfold_timer);
h_scroll->connect(SceneStringName(value_changed), callable_mp(this, &Tree::_scroll_moved)); h_scroll->connect(SceneStringName(value_changed), callable_mp(this, &Tree::_scroll_moved));
v_scroll->connect(SceneStringName(value_changed), callable_mp(this, &Tree::_scroll_moved)); v_scroll->connect(SceneStringName(value_changed), callable_mp(this, &Tree::_scroll_moved));
line_editor->connect(SceneStringName(text_submitted), callable_mp(this, &Tree::_line_editor_submit)); line_editor->connect(SceneStringName(text_submitted), callable_mp(this, &Tree::_line_editor_submit));

View file

@ -650,6 +650,8 @@ private:
int parent_hl_line_margin = 0; int parent_hl_line_margin = 0;
int draw_guides = 0; int draw_guides = 0;
int dragging_unfold_wait_msec = 500;
int scroll_border = 0; int scroll_border = 0;
int scroll_speed = 0; int scroll_speed = 0;
@ -737,6 +739,11 @@ private:
bool enable_recursive_folding = true; bool enable_recursive_folding = true;
bool enable_drag_unfolding = true;
Timer *dropping_unfold_timer = nullptr;
void _on_dropping_unfold_timer_timeout();
void _reset_drop_mode_over();
bool enable_auto_tooltip = true; bool enable_auto_tooltip = true;
bool hovered_update_queued = false; bool hovered_update_queued = false;
@ -885,6 +892,9 @@ public:
void set_enable_recursive_folding(bool p_enable); void set_enable_recursive_folding(bool p_enable);
bool is_recursive_folding_enabled() const; bool is_recursive_folding_enabled() const;
void set_enable_drag_unfolding(bool p_enable);
bool is_drag_unfolding_enabled() const;
void set_drop_mode_flags(int p_flags); void set_drop_mode_flags(int p_flags);
int get_drop_mode_flags() const; int get_drop_mode_flags() const;

View file

@ -912,6 +912,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("children_hl_line_width", "Tree", 1); theme->set_constant("children_hl_line_width", "Tree", 1);
theme->set_constant("parent_hl_line_margin", "Tree", 0); theme->set_constant("parent_hl_line_margin", "Tree", 0);
theme->set_constant("draw_guides", "Tree", 1); theme->set_constant("draw_guides", "Tree", 1);
theme->set_constant("dragging_unfold_wait_msec", "Tree", 500);
theme->set_constant("scroll_border", "Tree", Math::round(4 * scale)); theme->set_constant("scroll_border", "Tree", Math::round(4 * scale));
theme->set_constant("scroll_speed", "Tree", 12); theme->set_constant("scroll_speed", "Tree", 12);
theme->set_constant("outline_size", "Tree", 0); theme->set_constant("outline_size", "Tree", 0);