diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index 4d605b0b607..19e2e9e3b72 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -270,6 +270,9 @@ If [code]true[/code], enables selecting a tab with the right mouse button. + + If [code]true[/code], hovering over a tab while dragging something will switch to that tab. Does not have effect when hovering another tab to rearrange. The delay for when this happens is dictated by [theme_item hover_switch_wait_msec]. + The position at which tabs will be placed. @@ -403,6 +406,9 @@ The horizontal separation between the elements inside tabs. + + During a drag-and-drop, this is how many milliseconds to wait before switching the tab. + The maximum allowed width of the tab's icon. This limit is applied on top of the default size of the icon, but before the value set with [method set_tab_icon_max_width]. The height is adjusted according to the icon's ratio. diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index d336f5d38b9..10777557345 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -223,6 +223,9 @@ If [code]true[/code], tabs can be rearranged with mouse drag. + + If [code]true[/code], hovering over a tab while dragging something will switch to that tab. Does not have effect when hovering another tab to rearrange. + The position at which tabs will be placed. diff --git a/editor/docks/editor_dock_manager.cpp b/editor/docks/editor_dock_manager.cpp index 9f519e7db38..d3212c662c7 100644 --- a/editor/docks/editor_dock_manager.cpp +++ b/editor/docks/editor_dock_manager.cpp @@ -248,8 +248,12 @@ EditorDock *EditorDockManager::_get_dock_tab_dragged() { Dictionary dock_drop_data = dock_slot[DOCK_SLOT_LEFT_BL]->get_viewport()->gui_get_drag_data(); // Check if we are dragging a dock. - const String type = dock_drop_data.get("type", ""); - if (type == "tab_container_tab") { + if (dock_drop_data.get("type", "").operator String() != "tab") { + return nullptr; + } + + const String tab_type = dock_drop_data.get("tab_type", ""); + if (tab_type == "tab_container_tab") { Node *source_tab_bar = EditorNode::get_singleton()->get_node(dock_drop_data["from_path"]); if (!source_tab_bar) { return nullptr; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index fb3d88a7e84..abc50032c93 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1017,6 +1017,7 @@ void EditorNode::_notification(int p_what) { 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); + theme->set_constant("hover_switch_wait_msec", "TabBar", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000); } if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/dock_tab_style")) { diff --git a/editor/inspector/editor_properties.cpp b/editor/inspector/editor_properties.cpp index c2a4dbe7b2b..784dd048210 100644 --- a/editor/inspector/editor_properties.cpp +++ b/editor/inspector/editor_properties.cpp @@ -3027,6 +3027,11 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const return false; } + Object *data_root = p_drag_data.get("scene_root", (Object *)nullptr); + if (data_root && get_tree()->get_edited_scene_root() != data_root) { + return false; + } + Node *dropped_node = get_tree()->get_edited_scene_root()->get_node(nodes[0]); ERR_FAIL_NULL_V(dropped_node, false); diff --git a/editor/scene/scene_tree_editor.cpp b/editor/scene/scene_tree_editor.cpp index ca74686ff3e..4f85224f73c 100644 --- a/editor/scene/scene_tree_editor.cpp +++ b/editor/scene/scene_tree_editor.cpp @@ -1874,6 +1874,7 @@ Variant SceneTreeEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from Dictionary drag_data; drag_data["type"] = "nodes"; drag_data["nodes"] = objs; + drag_data["scene_root"] = get_tree()->get_edited_scene_root(); tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM); emit_signal(SNAME("nodes_dragged")); @@ -1895,6 +1896,11 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_d return false; } + Object *data_root = d.get("scene_root", (Object *)nullptr); + if (data_root && get_tree()->get_edited_scene_root() != data_root) { + return false; + } + TreeItem *item = (p_point == Vector2(Math::INF, Math::INF)) ? tree->get_selected() : tree->get_item_at_position(p_point); if (!item) { return false; diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 86d3ef39803..f937a1d4703 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1204,6 +1204,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref &p_the p_theme->set_constant("outline_size", "TabContainer", 0); p_theme->set_constant("h_separation", "TabBar", 4 * EDSCALE); p_theme->set_constant("outline_size", "TabBar", 0); + p_theme->set_constant("hover_switch_wait_msec", "TabBar", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000); } // Separators. diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 7715b3e08af..36bb60b2db5 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -33,6 +33,7 @@ #include "scene/gui/box_container.h" #include "scene/gui/label.h" #include "scene/gui/texture_rect.h" +#include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/theme/theme_db.h" @@ -501,6 +502,13 @@ void TabBar::_notification(int p_what) { dragging_valid_tab = false; queue_redraw(); } + [[fallthrough]]; + } + + case NOTIFICATION_MOUSE_EXIT: { + if (!hover_switch_delay->is_stopped()) { + hover_switch_delay->stop(); + } } break; case NOTIFICATION_DRAW: { @@ -1285,6 +1293,10 @@ void TabBar::_update_cache(bool p_update_hover) { } } +void TabBar::_hover_switch_timeout() { + set_current_tab(hover); +} + void TabBar::_on_mouse_exited() { rb_hover = -1; cb_hover = -1; @@ -1424,6 +1436,10 @@ Variant TabBar::get_drag_data(const Point2 &p_point) { } bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + if (switch_on_drag_hover) { + _handle_switch_on_hover(p_data); + } + bool drop_override = Control::can_drop_data(p_point, p_data); if (drop_override) { return drop_override; @@ -1470,7 +1486,8 @@ Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_poin set_drag_preview(drag_preview); Dictionary drag_data; - drag_data["type"] = p_type; + drag_data["type"] = "tab"; + drag_data["tab_type"] = p_type; drag_data["tab_index"] = tab_over; drag_data["from_path"] = get_path(); @@ -1479,11 +1496,12 @@ Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_poin bool TabBar::_handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const { Dictionary d = p_data; - if (!d.has("type")) { + if (d.get("type", "").operator String() != "tab") { return false; } - if (String(d["type"]) == p_type) { + const String tab_type = d.get("tab_type", ""); + if (tab_type == p_type) { NodePath from_path = d["from_path"]; NodePath to_path = get_path(); if (from_path == to_path) { @@ -1497,17 +1515,17 @@ bool TabBar::_handle_can_drop_data(const String &p_type, const Point2 &p_point, } } } - return false; } void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback) { Dictionary d = p_data; - if (!d.has("type")) { + if (d.get("type", "").operator String() != "tab") { return; } - if (String(d["type"]) == p_type) { + const String tab_type = d.get("tab_type", ""); + if (tab_type == p_type) { int tab_from_id = d["tab_index"]; int hover_now = (p_point == Vector2(Math::INF, Math::INF)) ? current : get_closest_tab_idx_to_point(p_point); NodePath from_path = d["from_path"]; @@ -1565,6 +1583,22 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons } } +void TabBar::_handle_switch_on_hover(const Variant &p_data) const { + Dictionary d = p_data; + if (d.get("type", "").operator String() == "tab") { + // Dragging a tab shouldn't switch on hover. + return; + } + + if (hover > -1 && hover != current) { + if (hover_switch_delay->is_stopped()) { + const_cast(this)->hover_switch_delay->start(theme_cache.hover_switch_wait_msec * 0.001); + } + } else if (!hover_switch_delay->is_stopped()) { + hover_switch_delay->stop(); + } +} + void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) { Tab moving_tab = p_from_tabbar->tabs[p_from_index]; moving_tab.accessibility_item_element = RID(); @@ -1983,6 +2017,14 @@ bool TabBar::get_scroll_to_selected() const { return scroll_to_selected; } +void TabBar::set_switch_on_drag_hover(bool p_enabled) { + switch_on_drag_hover = p_enabled; +} + +bool TabBar::get_switch_on_drag_hover() const { + return switch_on_drag_hover; +} + void TabBar::set_select_with_rmb(bool p_enabled) { select_with_rmb = p_enabled; } @@ -2055,6 +2097,8 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &TabBar::get_scrolling_enabled); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabBar::set_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabBar::get_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("set_switch_on_drag_hover", "enabled"), &TabBar::set_switch_on_drag_hover); + ClassDB::bind_method(D_METHOD("get_switch_on_drag_hover"), &TabBar::get_switch_on_drag_hover); ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabBar::set_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabBar::get_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("set_scroll_to_selected", "enabled"), &TabBar::set_scroll_to_selected); @@ -2082,6 +2126,7 @@ void TabBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "max_tab_width", PROPERTY_HINT_RANGE, "0,99999,1,suffix:px"), "set_max_tab_width", "get_max_tab_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_drag_hover"), "set_switch_on_drag_hover", "get_switch_on_drag_hover"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb"); @@ -2102,6 +2147,7 @@ void TabBar::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, h_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, tab_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, icon_max_width); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, hover_switch_wait_msec); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_unselected_style, "tab_unselected"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_hovered_style, "tab_hovered"); @@ -2152,5 +2198,10 @@ TabBar::TabBar() { set_focus_mode(FOCUS_ALL); connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited)); + hover_switch_delay = memnew(Timer); + hover_switch_delay->connect("timeout", callable_mp(this, &TabBar::_hover_switch_timeout)); + hover_switch_delay->set_one_shot(true); + add_child(hover_switch_delay, false, INTERNAL_MODE_FRONT); + property_helper.setup_for_instance(base_property_helper, this); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index fe7555567f7..617d4c1de0e 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -34,6 +34,8 @@ #include "scene/property_list_helper.h" #include "scene/resources/text_line.h" +class Timer; + class TabBar : public Control { GDCLASS(TabBar, Control); @@ -130,6 +132,7 @@ private: bool dragging_valid_tab = false; bool scroll_to_selected = true; int tabs_rearrange_group = -1; + bool switch_on_drag_hover = true; static const int CURRENT_TAB_UNINITIALIZED = -2; bool initialized = false; @@ -143,6 +146,7 @@ private: int h_separation = 0; int tab_separation = 0; int icon_max_width = 0; + int hover_switch_wait_msec = 500; Ref tab_unselected_style; Ref tab_hovered_style; @@ -177,6 +181,8 @@ private: Ref button_hl_style; } theme_cache; + Timer *hover_switch_delay = nullptr; + int get_tab_width(int p_idx) const; Size2 _get_tab_icon_size(int p_idx) const; void _ensure_no_over_offset(); @@ -184,6 +190,7 @@ private: void _update_hover(); void _update_cache(bool p_update_hover = true); + void _hover_switch_timeout(); void _on_mouse_exited(); @@ -218,6 +225,7 @@ public: bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const; void _handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback); void _draw_tab_drop(RID p_canvas_item); + void _handle_switch_on_hover(const Variant &p_data) const; void add_tab(const String &p_str = "", const Ref &p_icon = Ref()); @@ -307,6 +315,9 @@ public: void set_scroll_to_selected(bool p_enabled); bool get_scroll_to_selected() const; + void set_switch_on_drag_hover(bool p_enabled); + bool get_switch_on_drag_hover() const; + void set_select_with_rmb(bool p_enabled); bool get_select_with_rmb() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index deb9d710d1c..9d6e4c62fe9 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -1025,6 +1025,14 @@ Popup *TabContainer::get_popup() const { return nullptr; } +void TabContainer::set_switch_on_drag_hover(bool p_enabled) { + tab_bar->set_switch_on_drag_hover(p_enabled); +} + +bool TabContainer::get_switch_on_drag_hover() const { + return tab_bar->get_switch_on_drag_hover(); +} + void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) { drag_to_rearrange_enabled = p_enabled; } @@ -1102,6 +1110,8 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_idx_from_control", "control"), &TabContainer::get_tab_idx_from_control); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); + ClassDB::bind_method(D_METHOD("set_switch_on_drag_hover", "enabled"), &TabContainer::set_switch_on_drag_hover); + ClassDB::bind_method(D_METHOD("get_switch_on_drag_hover"), &TabContainer::get_switch_on_drag_hover); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabContainer::get_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabContainer::set_tabs_rearrange_group); @@ -1127,6 +1137,7 @@ void TabContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "all_tabs_in_front"), "set_all_tabs_in_front", "is_all_tabs_in_front"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_drag_hover"), "set_switch_on_drag_hover", "get_switch_on_drag_hover"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hidden_tabs_for_min_size"), "set_use_hidden_tabs_for_min_size", "get_use_hidden_tabs_for_min_size"); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index c77b50ab1e9..66ece7c76b9 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -207,6 +207,8 @@ public: void move_tab_from_tab_container(TabContainer *p_from, int p_from_index, int p_to_index = -1); + void set_switch_on_drag_hover(bool p_enabled); + bool get_switch_on_drag_hover() const; void set_drag_to_rearrange_enabled(bool p_enabled); bool get_drag_to_rearrange_enabled() const; void set_tabs_rearrange_group(int p_group_id); diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 2294f45e03b..8eb6428ce3f 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -1040,6 +1040,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_constant("h_separation", "TabBar", Math::round(4 * scale)); theme->set_constant("icon_max_width", "TabBar", 0); theme->set_constant("outline_size", "TabBar", 0); + theme->set_constant("hover_switch_wait_msec", "TabBar", 500); // Separators