From 117d5cbf960d4f388d64cb93e105dcf4ea3a42c4 Mon Sep 17 00:00:00 2001 From: Aaron J Yoder Date: Wed, 5 Mar 2025 16:55:37 -0500 Subject: [PATCH] Add tab spacing modifier for tabs in TabBar and TabContainer --- doc/classes/TabBar.xml | 3 ++ doc/classes/TabContainer.xml | 3 ++ scene/gui/tab_bar.cpp | 91 +++++++++++++++++++++++++++++++----- scene/gui/tab_bar.h | 2 + scene/gui/tab_container.cpp | 2 + scene/gui/tab_container.h | 1 + 6 files changed, 91 insertions(+), 11 deletions(-) diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index d080294c1a4..3f808f792fc 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -395,6 +395,9 @@ The size of the tab text outline. [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. + + The space between tabs in the tab bar. + The font used to draw tab names. diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index 090afa02207..8244ee0fe0b 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -330,6 +330,9 @@ The space at the left or right edges of the tab bar, accordingly with the current [member tab_alignment]. 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]. + + The space between tabs in the tab bar. + The font used to draw tab names. diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 65e3d23c0bf..a8d9adc453d 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -98,6 +98,10 @@ Size2 TabBar::get_minimum_size() const { if (ms.width - ofs > style->get_minimum_size().width) { ms.width -= theme_cache.h_separation; } + + if (i < tabs.size() - 1) { + ms.width += theme_cache.tab_separation; + } } if (clip_tabs) { @@ -430,9 +434,12 @@ void TabBar::_notification(int p_what) { int limit_minus_buttons = size.width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int ofs = tabs[offset].ofs_cache; + int tab_separation_offset = 0; // Draw unselected tabs in the back. for (int i = offset; i <= max_drawn_tab; i++) { + tab_separation_offset = (i - offset) * theme_cache.tab_separation; + if (tabs[i].hidden) { continue; } @@ -452,7 +459,7 @@ void TabBar::_notification(int p_what) { col = theme_cache.font_unselected_color; } - _draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs, false); + _draw_tab(sb, col, i, rtl ? (size.width - ofs - tab_separation_offset - tabs[i].size_cache) : (ofs + tab_separation_offset), false); } ofs += tabs[i].size_cache; @@ -460,8 +467,13 @@ void TabBar::_notification(int p_what) { // Draw selected tab in the front, but only if it's visible. if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) { + tab_separation_offset = (current - offset) * theme_cache.tab_separation; + if (tab_alignment == ALIGNMENT_LEFT && (current - offset) > 1) { + tab_separation_offset = theme_cache.tab_separation; + } + Ref sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style; - float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache; + float x = rtl ? (size.width - tabs[current].ofs_cache - tab_separation_offset - tabs[current].size_cache) : (tabs[current].ofs_cache + tab_separation_offset); _draw_tab(sb, theme_cache.font_selected_color, current, x, has_focus()); } @@ -499,13 +511,41 @@ void TabBar::_notification(int p_what) { if (dragging_valid_tab) { int x; - int tab_hover = get_hovered_tab(); - if (tab_hover != -1) { - Rect2 tab_rect = get_tab_rect(tab_hover); + int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position()); + if (closest_tab != -1) { + Rect2 tab_rect = get_tab_rect(closest_tab); - x = tab_rect.position.x; - if (get_local_mouse_position().x > x + tab_rect.size.width / 2) { - x += tab_rect.size.width; + // Calculate midpoint between tabs. + if (rtl) { + if (get_local_mouse_position().x > tab_rect.position.x + tab_rect.size.width / 2) { + if (closest_tab > 0) { // On right side of closest_tab and not first tab. + Rect2 next_tab_rect = get_tab_rect(closest_tab - 1); + x = (tab_rect.position.x + tab_rect.size.width + next_tab_rect.position.x) / 2; + } else { // First tab, will appear on right edge. + x = tab_rect.position.x + tab_rect.size.width; + } + } else { + if (closest_tab < max_drawn_tab) { // On left side of closest_tab and not last tab. + Rect2 prev_tab_rect = get_tab_rect(closest_tab + 1); + x = (tab_rect.position.x + prev_tab_rect.position.x + prev_tab_rect.size.width) / 2; + } else { // Last tab, will appear on left edge. + x = tab_rect.position.x; + } + } + } else if (get_local_mouse_position().x > tab_rect.position.x + tab_rect.size.width / 2) { + if (closest_tab < max_drawn_tab) { // On right side of closest_tab and not last tab. + Rect2 next_tab_rect = get_tab_rect(closest_tab + 1); + x = (tab_rect.position.x + tab_rect.size.width + next_tab_rect.position.x) / 2; + } else { // Last tab, will appear on right edge. + x = tab_rect.position.x + tab_rect.size.width; + } + } else { + if (closest_tab > 0) { // On left side of closest_tab and not first tab. + Rect2 prev_tab_rect = get_tab_rect(closest_tab - 1); + x = (tab_rect.position.x + prev_tab_rect.position.x + prev_tab_rect.size.width) / 2; + } else { // First tab, will appear on left edge. + x = tab_rect.position.x; + } } } else { if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) { @@ -1053,12 +1093,17 @@ void TabBar::_update_cache(bool p_update_hover) { } w += tabs[i].size_cache; + if ((i - offset) > 0) { + w += theme_cache.tab_separation; + } // Check if all tabs would fit inside the area. if (clip_tabs && i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) { tabs.write[i].ofs_cache = 0; w -= tabs[i].size_cache; + w -= theme_cache.tab_separation; + max_drawn_tab = i - 1; while (w > limit_minus_buttons && max_drawn_tab > offset) { @@ -1066,6 +1111,7 @@ void TabBar::_update_cache(bool p_update_hover) { if (!tabs[max_drawn_tab].hidden) { w -= tabs[max_drawn_tab].size_cache; + w -= theme_cache.tab_separation; } max_drawn_tab--; @@ -1303,7 +1349,7 @@ void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, cons if (String(d["type"]) == p_type) { int tab_from_id = d["tab_index"]; - int hover_now = get_tab_idx_at_point(p_point); + int hover_now = get_closest_tab_idx_to_point(p_point); NodePath from_path = d["from_path"]; NodePath to_path = get_path(); @@ -1398,6 +1444,24 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const { return hover_now; } +int TabBar::get_closest_tab_idx_to_point(const Point2 &p_point) const { + int closest_tab = get_tab_idx_at_point(p_point); // See if we're hovering over a tab first. + + if (closest_tab == -1) { // Didn't find a tab, so get the closest one. + float closest_distance = FLT_MAX; + for (int i = offset; i <= max_drawn_tab; i++) { + Vector2 center = get_tab_rect(i).get_center(); + float distance = center.distance_to(p_point); + if (distance < closest_distance) { + closest_distance = distance; + closest_tab = i; + } + } + } + + return closest_tab; +} + void TabBar::set_tab_alignment(AlignmentMode p_alignment) { ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX); @@ -1647,10 +1711,14 @@ void TabBar::ensure_tab_visible(int p_idx) { Rect2 TabBar::get_tab_rect(int p_tab) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2()); + int tab_separation_offset = (p_tab - offset) * theme_cache.tab_separation; + if (tab_alignment == ALIGNMENT_LEFT && (p_tab - offset) > 1) { + tab_separation_offset = theme_cache.tab_separation; + } if (is_layout_rtl()) { - return Rect2(get_size().width - tabs[p_tab].ofs_cache - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height); + 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); } else { - return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height); + return Rect2(tabs[p_tab].ofs_cache + tab_separation_offset, 0, tabs[p_tab].size_cache, get_size().height); } } @@ -1847,6 +1915,7 @@ void TabBar::_bind_methods() { BIND_ENUM_CONSTANT(CLOSE_BUTTON_MAX); 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_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_unselected_style, "tab_unselected"); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index f5ae75de51f..55c375db37e 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -127,6 +127,7 @@ private: struct ThemeCache { int h_separation = 0; + int tab_separation = 0; int icon_max_width = 0; Ref tab_unselected_style; @@ -225,6 +226,7 @@ public: Ref get_tab_button_icon(int p_tab) const; int get_tab_idx_at_point(const Point2 &p_point) const; + int get_closest_tab_idx_to_point(const Point2 &p_point) const; void set_tab_alignment(AlignmentMode p_alignment); AlignmentMode get_tab_alignment() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index c43d0c5d55c..cc80a158eb1 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -247,6 +247,7 @@ void TabContainer::_on_theme_changed() { tab_bar->add_theme_font_size_override(SceneStringName(font_size), theme_cache.tab_font_size); tab_bar->add_theme_constant_override(SNAME("h_separation"), theme_cache.icon_separation); + tab_bar->add_theme_constant_override(SNAME("tab_separation"), theme_cache.tab_separation); tab_bar->add_theme_constant_override(SNAME("icon_max_width"), theme_cache.icon_max_width); tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size); @@ -1078,6 +1079,7 @@ void TabContainer::_bind_methods() { BIND_ENUM_CONSTANT(POSITION_MAX); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, tab_separation); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, panel_style, "panel"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tabbar_style, "tabbar_background"); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index e00bc780d47..a2d3cc4cfdf 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -71,6 +71,7 @@ private: // TabBar overrides. int icon_separation = 0; + int tab_separation = 0; int icon_max_width = 0; int outline_size = 0;