Skip to content

Commit 777c663

Browse files
committed
Merge pull request #103214 from aaronjyoder/master
Add tab spacing modifier for tabs in TabBar and TabContainer
2 parents 9a0ea64 + 117d5cb commit 777c663

File tree

6 files changed

+92
-12
lines changed

6 files changed

+92
-12
lines changed

doc/classes/TabBar.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@
395395
The size of the tab text outline.
396396
[b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended.
397397
</theme_item>
398+
<theme_item name="tab_separation" data_type="constant" type="int" default="0">
399+
The space between tabs in the tab bar.
400+
</theme_item>
398401
<theme_item name="font" data_type="font" type="Font">
399402
The font used to draw tab names.
400403
</theme_item>

doc/classes/TabContainer.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@
330330
The space at the left or right edges of the tab bar, accordingly with the current [member tab_alignment].
331331
The margin is ignored with [constant TabBar.ALIGNMENT_RIGHT] if the tabs are clipped (see [member clip_tabs]) or a popup has been set (see [method set_popup]). The margin is always ignored with [constant TabBar.ALIGNMENT_CENTER].
332332
</theme_item>
333+
<theme_item name="tab_separation" data_type="constant" type="int" default="0">
334+
The space between tabs in the tab bar.
335+
</theme_item>
333336
<theme_item name="font" data_type="font" type="Font">
334337
The font used to draw tab names.
335338
</theme_item>

scene/gui/tab_bar.cpp

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ Size2 TabBar::get_minimum_size() const {
9898
if (ms.width - ofs > style->get_minimum_size().width) {
9999
ms.width -= theme_cache.h_separation;
100100
}
101+
102+
if (i < tabs.size() - 1) {
103+
ms.width += theme_cache.tab_separation;
104+
}
101105
}
102106

103107
if (clip_tabs) {
@@ -430,9 +434,12 @@ void TabBar::_notification(int p_what) {
430434
int limit_minus_buttons = size.width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width();
431435

432436
int ofs = tabs[offset].ofs_cache;
437+
int tab_separation_offset = 0;
433438

434439
// Draw unselected tabs in the back.
435440
for (int i = offset; i <= max_drawn_tab; i++) {
441+
tab_separation_offset = (i - offset) * theme_cache.tab_separation;
442+
436443
if (tabs[i].hidden) {
437444
continue;
438445
}
@@ -452,16 +459,21 @@ void TabBar::_notification(int p_what) {
452459
col = theme_cache.font_unselected_color;
453460
}
454461

455-
_draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs, false);
462+
_draw_tab(sb, col, i, rtl ? (size.width - ofs - tab_separation_offset - tabs[i].size_cache) : (ofs + tab_separation_offset), false);
456463
}
457464

458465
ofs += tabs[i].size_cache;
459466
}
460467

461468
// Draw selected tab in the front, but only if it's visible.
462469
if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) {
470+
tab_separation_offset = (current - offset) * theme_cache.tab_separation;
471+
if (tab_alignment == ALIGNMENT_LEFT && (current - offset) > 1) {
472+
tab_separation_offset = theme_cache.tab_separation;
473+
}
474+
463475
Ref<StyleBox> sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style;
464-
float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache;
476+
float x = rtl ? (size.width - tabs[current].ofs_cache - tab_separation_offset - tabs[current].size_cache) : (tabs[current].ofs_cache + tab_separation_offset);
465477

466478
_draw_tab(sb, theme_cache.font_selected_color, current, x, has_focus());
467479
}
@@ -499,13 +511,41 @@ void TabBar::_notification(int p_what) {
499511
if (dragging_valid_tab) {
500512
int x;
501513

502-
int tab_hover = get_hovered_tab();
503-
if (tab_hover != -1) {
504-
Rect2 tab_rect = get_tab_rect(tab_hover);
505-
506-
x = tab_rect.position.x;
507-
if (get_local_mouse_position().x > x + tab_rect.size.width / 2) {
508-
x += tab_rect.size.width;
514+
int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position());
515+
if (closest_tab != -1) {
516+
Rect2 tab_rect = get_tab_rect(closest_tab);
517+
518+
// Calculate midpoint between tabs.
519+
if (rtl) {
520+
if (get_local_mouse_position().x > tab_rect.position.x + tab_rect.size.width / 2) {
521+
if (closest_tab > 0) { // On right side of closest_tab and not first tab.
522+
Rect2 next_tab_rect = get_tab_rect(closest_tab - 1);
523+
x = (tab_rect.position.x + tab_rect.size.width + next_tab_rect.position.x) / 2;
524+
} else { // First tab, will appear on right edge.
525+
x = tab_rect.position.x + tab_rect.size.width;
526+
}
527+
} else {
528+
if (closest_tab < max_drawn_tab) { // On left side of closest_tab and not last tab.
529+
Rect2 prev_tab_rect = get_tab_rect(closest_tab + 1);
530+
x = (tab_rect.position.x + prev_tab_rect.position.x + prev_tab_rect.size.width) / 2;
531+
} else { // Last tab, will appear on left edge.
532+
x = tab_rect.position.x;
533+
}
534+
}
535+
} else if (get_local_mouse_position().x > tab_rect.position.x + tab_rect.size.width / 2) {
536+
if (closest_tab < max_drawn_tab) { // On right side of closest_tab and not last tab.
537+
Rect2 next_tab_rect = get_tab_rect(closest_tab + 1);
538+
x = (tab_rect.position.x + tab_rect.size.width + next_tab_rect.position.x) / 2;
539+
} else { // Last tab, will appear on right edge.
540+
x = tab_rect.position.x + tab_rect.size.width;
541+
}
542+
} else {
543+
if (closest_tab > 0) { // On left side of closest_tab and not first tab.
544+
Rect2 prev_tab_rect = get_tab_rect(closest_tab - 1);
545+
x = (tab_rect.position.x + prev_tab_rect.position.x + prev_tab_rect.size.width) / 2;
546+
} else { // First tab, will appear on left edge.
547+
x = tab_rect.position.x;
548+
}
509549
}
510550
} else {
511551
if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) {
@@ -1053,19 +1093,25 @@ void TabBar::_update_cache(bool p_update_hover) {
10531093
}
10541094

10551095
w += tabs[i].size_cache;
1096+
if ((i - offset) > 0) {
1097+
w += theme_cache.tab_separation;
1098+
}
10561099

10571100
// Check if all tabs would fit inside the area.
10581101
if (clip_tabs && i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) {
10591102
tabs.write[i].ofs_cache = 0;
10601103

10611104
w -= tabs[i].size_cache;
1105+
w -= theme_cache.tab_separation;
1106+
10621107
max_drawn_tab = i - 1;
10631108

10641109
while (w > limit_minus_buttons && max_drawn_tab > offset) {
10651110
tabs.write[max_drawn_tab].ofs_cache = 0;
10661111

10671112
if (!tabs[max_drawn_tab].hidden) {
10681113
w -= tabs[max_drawn_tab].size_cache;
1114+
w -= theme_cache.tab_separation;
10691115
}
10701116

10711117
max_drawn_tab--;
@@ -1303,7 +1349,7 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons
13031349

13041350
if (String(d["type"]) == p_type) {
13051351
int tab_from_id = d["tab_index"];
1306-
int hover_now = get_tab_idx_at_point(p_point);
1352+
int hover_now = get_closest_tab_idx_to_point(p_point);
13071353
NodePath from_path = d["from_path"];
13081354
NodePath to_path = get_path();
13091355

@@ -1398,6 +1444,24 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const {
13981444
return hover_now;
13991445
}
14001446

1447+
int TabBar::get_closest_tab_idx_to_point(const Point2 &p_point) const {
1448+
int closest_tab = get_tab_idx_at_point(p_point); // See if we're hovering over a tab first.
1449+
1450+
if (closest_tab == -1) { // Didn't find a tab, so get the closest one.
1451+
float closest_distance = FLT_MAX;
1452+
for (int i = offset; i <= max_drawn_tab; i++) {
1453+
Vector2 center = get_tab_rect(i).get_center();
1454+
float distance = center.distance_to(p_point);
1455+
if (distance < closest_distance) {
1456+
closest_distance = distance;
1457+
closest_tab = i;
1458+
}
1459+
}
1460+
}
1461+
1462+
return closest_tab;
1463+
}
1464+
14011465
void TabBar::set_tab_alignment(AlignmentMode p_alignment) {
14021466
ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX);
14031467

@@ -1647,10 +1711,14 @@ void TabBar::ensure_tab_visible(int p_idx) {
16471711

16481712
Rect2 TabBar::get_tab_rect(int p_tab) const {
16491713
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2());
1714+
int tab_separation_offset = (p_tab - offset) * theme_cache.tab_separation;
1715+
if (tab_alignment == ALIGNMENT_LEFT && (p_tab - offset) > 1) {
1716+
tab_separation_offset = theme_cache.tab_separation;
1717+
}
16501718
if (is_layout_rtl()) {
1651-
return Rect2(get_size().width - tabs[p_tab].ofs_cache - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height);
1719+
return Rect2(get_size().width - tabs[p_tab].ofs_cache - tab_separation_offset - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height);
16521720
} else {
1653-
return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height);
1721+
return Rect2(tabs[p_tab].ofs_cache + tab_separation_offset, 0, tabs[p_tab].size_cache, get_size().height);
16541722
}
16551723
}
16561724

@@ -1847,6 +1915,7 @@ void TabBar::_bind_methods() {
18471915
BIND_ENUM_CONSTANT(CLOSE_BUTTON_MAX);
18481916

18491917
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, h_separation);
1918+
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, tab_separation);
18501919
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabBar, icon_max_width);
18511920

18521921
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_unselected_style, "tab_unselected");

scene/gui/tab_bar.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class TabBar : public Control {
126126

127127
struct ThemeCache {
128128
int h_separation = 0;
129+
int tab_separation = 0;
129130
int icon_max_width = 0;
130131

131132
Ref<StyleBox> tab_unselected_style;
@@ -224,6 +225,7 @@ class TabBar : public Control {
224225
Ref<Texture2D> get_tab_button_icon(int p_tab) const;
225226

226227
int get_tab_idx_at_point(const Point2 &p_point) const;
228+
int get_closest_tab_idx_to_point(const Point2 &p_point) const;
227229

228230
void set_tab_alignment(AlignmentMode p_alignment);
229231
AlignmentMode get_tab_alignment() const;

scene/gui/tab_container.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ void TabContainer::_on_theme_changed() {
247247
tab_bar->add_theme_font_size_override(SceneStringName(font_size), theme_cache.tab_font_size);
248248

249249
tab_bar->add_theme_constant_override(SNAME("h_separation"), theme_cache.icon_separation);
250+
tab_bar->add_theme_constant_override(SNAME("tab_separation"), theme_cache.tab_separation);
250251
tab_bar->add_theme_constant_override(SNAME("icon_max_width"), theme_cache.icon_max_width);
251252
tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size);
252253

@@ -1077,6 +1078,7 @@ void TabContainer::_bind_methods() {
10771078
BIND_ENUM_CONSTANT(POSITION_MAX);
10781079

10791080
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin);
1081+
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, tab_separation);
10801082

10811083
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, panel_style, "panel");
10821084
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tabbar_style, "tabbar_background");

scene/gui/tab_container.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class TabContainer : public Container {
7070

7171
// TabBar overrides.
7272
int icon_separation = 0;
73+
int tab_separation = 0;
7374
int icon_max_width = 0;
7475
int outline_size = 0;
7576

0 commit comments

Comments
 (0)