Skip to content

Commit 8191042

Browse files
committed
Merge pull request #103478 from KoBeWi/hover_witch
Add switch on hover to TabBar
2 parents ec62f12 + 682b0f7 commit 8191042

File tree

12 files changed

+110
-8
lines changed

12 files changed

+110
-8
lines changed

doc/classes/TabBar.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@
270270
<member name="select_with_rmb" type="bool" setter="set_select_with_rmb" getter="get_select_with_rmb" default="false">
271271
If [code]true[/code], enables selecting a tab with the right mouse button.
272272
</member>
273+
<member name="switch_on_drag_hover" type="bool" setter="set_switch_on_drag_hover" getter="get_switch_on_drag_hover" default="true">
274+
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].
275+
</member>
273276
<member name="tab_alignment" type="int" setter="set_tab_alignment" getter="get_tab_alignment" enum="TabBar.AlignmentMode" default="0">
274277
The position at which tabs will be placed.
275278
</member>
@@ -403,6 +406,9 @@
403406
<theme_item name="h_separation" data_type="constant" type="int" default="4">
404407
The horizontal separation between the elements inside tabs.
405408
</theme_item>
409+
<theme_item name="hover_switch_wait_msec" data_type="constant" type="int" default="500">
410+
During a drag-and-drop, this is how many milliseconds to wait before switching the tab.
411+
</theme_item>
406412
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
407413
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.
408414
</theme_item>

doc/classes/TabContainer.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@
223223
<member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled" default="false">
224224
If [code]true[/code], tabs can be rearranged with mouse drag.
225225
</member>
226+
<member name="switch_on_drag_hover" type="bool" setter="set_switch_on_drag_hover" getter="get_switch_on_drag_hover" default="true">
227+
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.
228+
</member>
226229
<member name="tab_alignment" type="int" setter="set_tab_alignment" getter="get_tab_alignment" enum="TabBar.AlignmentMode" default="0">
227230
The position at which tabs will be placed.
228231
</member>

editor/docks/editor_dock_manager.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,12 @@ EditorDock *EditorDockManager::_get_dock_tab_dragged() {
248248
Dictionary dock_drop_data = dock_slot[DOCK_SLOT_LEFT_BL]->get_viewport()->gui_get_drag_data();
249249

250250
// Check if we are dragging a dock.
251-
const String type = dock_drop_data.get("type", "");
252-
if (type == "tab_container_tab") {
251+
if (dock_drop_data.get("type", "").operator String() != "tab") {
252+
return nullptr;
253+
}
254+
255+
const String tab_type = dock_drop_data.get("tab_type", "");
256+
if (tab_type == "tab_container_tab") {
253257
Node *source_tab_bar = EditorNode::get_singleton()->get_node(dock_drop_data["from_path"]);
254258
if (!source_tab_bar) {
255259
return nullptr;

editor/editor_node.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ void EditorNode::_notification(int p_what) {
10171017

10181018
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) {
10191019
theme->set_constant("dragging_unfold_wait_msec", "Tree", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000);
1020+
theme->set_constant("hover_switch_wait_msec", "TabBar", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000);
10201021
}
10211022

10221023
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/dock_tab_style")) {

editor/inspector/editor_properties.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3027,6 +3027,11 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const
30273027
return false;
30283028
}
30293029

3030+
Object *data_root = p_drag_data.get("scene_root", (Object *)nullptr);
3031+
if (data_root && get_tree()->get_edited_scene_root() != data_root) {
3032+
return false;
3033+
}
3034+
30303035
Node *dropped_node = get_tree()->get_edited_scene_root()->get_node(nodes[0]);
30313036
ERR_FAIL_NULL_V(dropped_node, false);
30323037

editor/scene/scene_tree_editor.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,7 @@ Variant SceneTreeEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from
18741874
Dictionary drag_data;
18751875
drag_data["type"] = "nodes";
18761876
drag_data["nodes"] = objs;
1877+
drag_data["scene_root"] = get_tree()->get_edited_scene_root();
18771878

18781879
tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM);
18791880
emit_signal(SNAME("nodes_dragged"));
@@ -1895,6 +1896,11 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_d
18951896
return false;
18961897
}
18971898

1899+
Object *data_root = d.get("scene_root", (Object *)nullptr);
1900+
if (data_root && get_tree()->get_edited_scene_root() != data_root) {
1901+
return false;
1902+
}
1903+
18981904
TreeItem *item = (p_point == Vector2(Math::INF, Math::INF)) ? tree->get_selected() : tree->get_item_at_position(p_point);
18991905
if (!item) {
19001906
return false;

editor/themes/editor_theme_manager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
12041204
p_theme->set_constant("outline_size", "TabContainer", 0);
12051205
p_theme->set_constant("h_separation", "TabBar", 4 * EDSCALE);
12061206
p_theme->set_constant("outline_size", "TabBar", 0);
1207+
p_theme->set_constant("hover_switch_wait_msec", "TabBar", (float)EDITOR_GET("interface/editor/dragging_hover_wait_seconds") * 1000);
12071208
}
12081209

12091210
// Separators.

scene/gui/tab_bar.cpp

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "scene/gui/box_container.h"
3434
#include "scene/gui/label.h"
3535
#include "scene/gui/texture_rect.h"
36+
#include "scene/main/timer.h"
3637
#include "scene/main/viewport.h"
3738
#include "scene/theme/theme_db.h"
3839

@@ -501,6 +502,13 @@ void TabBar::_notification(int p_what) {
501502
dragging_valid_tab = false;
502503
queue_redraw();
503504
}
505+
[[fallthrough]];
506+
}
507+
508+
case NOTIFICATION_MOUSE_EXIT: {
509+
if (!hover_switch_delay->is_stopped()) {
510+
hover_switch_delay->stop();
511+
}
504512
} break;
505513

506514
case NOTIFICATION_DRAW: {
@@ -1285,6 +1293,10 @@ void TabBar::_update_cache(bool p_update_hover) {
12851293
}
12861294
}
12871295

1296+
void TabBar::_hover_switch_timeout() {
1297+
set_current_tab(hover);
1298+
}
1299+
12881300
void TabBar::_on_mouse_exited() {
12891301
rb_hover = -1;
12901302
cb_hover = -1;
@@ -1424,6 +1436,10 @@ Variant TabBar::get_drag_data(const Point2 &p_point) {
14241436
}
14251437

14261438
bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
1439+
if (switch_on_drag_hover) {
1440+
_handle_switch_on_hover(p_data);
1441+
}
1442+
14271443
bool drop_override = Control::can_drop_data(p_point, p_data);
14281444
if (drop_override) {
14291445
return drop_override;
@@ -1470,7 +1486,8 @@ Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_poin
14701486
set_drag_preview(drag_preview);
14711487

14721488
Dictionary drag_data;
1473-
drag_data["type"] = p_type;
1489+
drag_data["type"] = "tab";
1490+
drag_data["tab_type"] = p_type;
14741491
drag_data["tab_index"] = tab_over;
14751492
drag_data["from_path"] = get_path();
14761493

@@ -1479,11 +1496,12 @@ Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_poin
14791496

14801497
bool TabBar::_handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const {
14811498
Dictionary d = p_data;
1482-
if (!d.has("type")) {
1499+
if (d.get("type", "").operator String() != "tab") {
14831500
return false;
14841501
}
14851502

1486-
if (String(d["type"]) == p_type) {
1503+
const String tab_type = d.get("tab_type", "");
1504+
if (tab_type == p_type) {
14871505
NodePath from_path = d["from_path"];
14881506
NodePath to_path = get_path();
14891507
if (from_path == to_path) {
@@ -1497,17 +1515,17 @@ bool TabBar::_handle_can_drop_data(const String &p_type, const Point2 &p_point,
14971515
}
14981516
}
14991517
}
1500-
15011518
return false;
15021519
}
15031520

15041521
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) {
15051522
Dictionary d = p_data;
1506-
if (!d.has("type")) {
1523+
if (d.get("type", "").operator String() != "tab") {
15071524
return;
15081525
}
15091526

1510-
if (String(d["type"]) == p_type) {
1527+
const String tab_type = d.get("tab_type", "");
1528+
if (tab_type == p_type) {
15111529
int tab_from_id = d["tab_index"];
15121530
int hover_now = (p_point == Vector2(Math::INF, Math::INF)) ? current : get_closest_tab_idx_to_point(p_point);
15131531
NodePath from_path = d["from_path"];
@@ -1565,6 +1583,22 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons
15651583
}
15661584
}
15671585

1586+
void TabBar::_handle_switch_on_hover(const Variant &p_data) const {
1587+
Dictionary d = p_data;
1588+
if (d.get("type", "").operator String() == "tab") {
1589+
// Dragging a tab shouldn't switch on hover.
1590+
return;
1591+
}
1592+
1593+
if (hover > -1 && hover != current) {
1594+
if (hover_switch_delay->is_stopped()) {
1595+
const_cast<TabBar *>(this)->hover_switch_delay->start(theme_cache.hover_switch_wait_msec * 0.001);
1596+
}
1597+
} else if (!hover_switch_delay->is_stopped()) {
1598+
hover_switch_delay->stop();
1599+
}
1600+
}
1601+
15681602
void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
15691603
Tab moving_tab = p_from_tabbar->tabs[p_from_index];
15701604
moving_tab.accessibility_item_element = RID();
@@ -1983,6 +2017,14 @@ bool TabBar::get_scroll_to_selected() const {
19832017
return scroll_to_selected;
19842018
}
19852019

2020+
void TabBar::set_switch_on_drag_hover(bool p_enabled) {
2021+
switch_on_drag_hover = p_enabled;
2022+
}
2023+
2024+
bool TabBar::get_switch_on_drag_hover() const {
2025+
return switch_on_drag_hover;
2026+
}
2027+
19862028
void TabBar::set_select_with_rmb(bool p_enabled) {
19872029
select_with_rmb = p_enabled;
19882030
}
@@ -2055,6 +2097,8 @@ void TabBar::_bind_methods() {
20552097
ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &TabBar::get_scrolling_enabled);
20562098
ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabBar::set_drag_to_rearrange_enabled);
20572099
ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabBar::get_drag_to_rearrange_enabled);
2100+
ClassDB::bind_method(D_METHOD("set_switch_on_drag_hover", "enabled"), &TabBar::set_switch_on_drag_hover);
2101+
ClassDB::bind_method(D_METHOD("get_switch_on_drag_hover"), &TabBar::get_switch_on_drag_hover);
20582102
ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabBar::set_tabs_rearrange_group);
20592103
ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabBar::get_tabs_rearrange_group);
20602104
ClassDB::bind_method(D_METHOD("set_scroll_to_selected", "enabled"), &TabBar::set_scroll_to_selected);
@@ -2082,6 +2126,7 @@ void TabBar::_bind_methods() {
20822126
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_tab_width", PROPERTY_HINT_RANGE, "0,99999,1,suffix:px"), "set_max_tab_width", "get_max_tab_width");
20832127
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled");
20842128
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled");
2129+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_drag_hover"), "set_switch_on_drag_hover", "get_switch_on_drag_hover");
20852130
ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group");
20862131
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected");
20872132
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb");
@@ -2102,6 +2147,7 @@ void TabBar::_bind_methods() {
21022147
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, h_separation);
21032148
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, tab_separation);
21042149
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, icon_max_width);
2150+
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, hover_switch_wait_msec);
21052151

21062152
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_unselected_style, "tab_unselected");
21072153
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_hovered_style, "tab_hovered");
@@ -2152,5 +2198,10 @@ TabBar::TabBar() {
21522198
set_focus_mode(FOCUS_ALL);
21532199
connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited));
21542200

2201+
hover_switch_delay = memnew(Timer);
2202+
hover_switch_delay->connect("timeout", callable_mp(this, &TabBar::_hover_switch_timeout));
2203+
hover_switch_delay->set_one_shot(true);
2204+
add_child(hover_switch_delay, false, INTERNAL_MODE_FRONT);
2205+
21552206
property_helper.setup_for_instance(base_property_helper, this);
21562207
}

scene/gui/tab_bar.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
#include "scene/property_list_helper.h"
3535
#include "scene/resources/text_line.h"
3636

37+
class Timer;
38+
3739
class TabBar : public Control {
3840
GDCLASS(TabBar, Control);
3941

@@ -130,6 +132,7 @@ class TabBar : public Control {
130132
bool dragging_valid_tab = false;
131133
bool scroll_to_selected = true;
132134
int tabs_rearrange_group = -1;
135+
bool switch_on_drag_hover = true;
133136

134137
static const int CURRENT_TAB_UNINITIALIZED = -2;
135138
bool initialized = false;
@@ -143,6 +146,7 @@ class TabBar : public Control {
143146
int h_separation = 0;
144147
int tab_separation = 0;
145148
int icon_max_width = 0;
149+
int hover_switch_wait_msec = 500;
146150

147151
Ref<StyleBox> tab_unselected_style;
148152
Ref<StyleBox> tab_hovered_style;
@@ -177,13 +181,16 @@ class TabBar : public Control {
177181
Ref<StyleBox> button_hl_style;
178182
} theme_cache;
179183

184+
Timer *hover_switch_delay = nullptr;
185+
180186
int get_tab_width(int p_idx) const;
181187
Size2 _get_tab_icon_size(int p_idx) const;
182188
void _ensure_no_over_offset();
183189
bool _can_deselect() const;
184190

185191
void _update_hover();
186192
void _update_cache(bool p_update_hover = true);
193+
void _hover_switch_timeout();
187194

188195
void _on_mouse_exited();
189196

@@ -218,6 +225,7 @@ class TabBar : public Control {
218225
bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const;
219226
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);
220227
void _draw_tab_drop(RID p_canvas_item);
228+
void _handle_switch_on_hover(const Variant &p_data) const;
221229

222230
void add_tab(const String &p_str = "", const Ref<Texture2D> &p_icon = Ref<Texture2D>());
223231

@@ -307,6 +315,9 @@ class TabBar : public Control {
307315
void set_scroll_to_selected(bool p_enabled);
308316
bool get_scroll_to_selected() const;
309317

318+
void set_switch_on_drag_hover(bool p_enabled);
319+
bool get_switch_on_drag_hover() const;
320+
310321
void set_select_with_rmb(bool p_enabled);
311322
bool get_select_with_rmb() const;
312323

scene/gui/tab_container.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,14 @@ Popup *TabContainer::get_popup() const {
10251025
return nullptr;
10261026
}
10271027

1028+
void TabContainer::set_switch_on_drag_hover(bool p_enabled) {
1029+
tab_bar->set_switch_on_drag_hover(p_enabled);
1030+
}
1031+
1032+
bool TabContainer::get_switch_on_drag_hover() const {
1033+
return tab_bar->get_switch_on_drag_hover();
1034+
}
1035+
10281036
void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) {
10291037
drag_to_rearrange_enabled = p_enabled;
10301038
}
@@ -1102,6 +1110,8 @@ void TabContainer::_bind_methods() {
11021110
ClassDB::bind_method(D_METHOD("get_tab_idx_from_control", "control"), &TabContainer::get_tab_idx_from_control);
11031111
ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup);
11041112
ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup);
1113+
ClassDB::bind_method(D_METHOD("set_switch_on_drag_hover", "enabled"), &TabContainer::set_switch_on_drag_hover);
1114+
ClassDB::bind_method(D_METHOD("get_switch_on_drag_hover"), &TabContainer::get_switch_on_drag_hover);
11051115
ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled);
11061116
ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabContainer::get_drag_to_rearrange_enabled);
11071117
ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabContainer::set_tabs_rearrange_group);
@@ -1127,6 +1137,7 @@ void TabContainer::_bind_methods() {
11271137
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs");
11281138
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible");
11291139
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "all_tabs_in_front"), "set_all_tabs_in_front", "is_all_tabs_in_front");
1140+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_drag_hover"), "set_switch_on_drag_hover", "get_switch_on_drag_hover");
11301141
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled");
11311142
ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group");
11321143
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");

0 commit comments

Comments
 (0)