From 5c4a414d3d50ece47b8254c07db70540525c73e5 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Sat, 5 Aug 2023 16:11:08 +0200 Subject: [PATCH] Allow adding buttons to the `TabContainer` header --- doc/classes/TabContainer.xml | 17 +++++++ scene/gui/tab_container.cpp | 87 +++++++++++++++++++++++++++++++---- scene/gui/tab_container.h | 10 ++++ scene/theme/default_theme.cpp | 1 + 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index 940eb89dab8a..831520e6cee8 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -11,6 +11,13 @@ $DOCS_URL/tutorials/ui/gui_containers.html + + + + + Add a [Button] to the tabs header. + + @@ -99,6 +106,13 @@ Returns [code]true[/code] if the tab at index [param tab_idx] is hidden. + + + + + Remove a [Button] from the tabs header. + + @@ -244,6 +258,9 @@ Font color of the other, unselected tabs. + + The size of the space between each of the buttons in the [TabBar]. + 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 TabBar.set_tab_icon_max_width]. The height is adjusted according to the icon's ratio. diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 14bc87ad4020..4c0ece4d3d98 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -31,6 +31,7 @@ #include "tab_container.h" #include "scene/gui/box_container.h" +#include "scene/gui/button.h" #include "scene/gui/label.h" #include "scene/gui/texture_rect.h" #include "scene/theme/theme_db.h" @@ -175,6 +176,17 @@ void TabContainer::_notification(int p_what) { theme_cache.menu_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_icon->get_height()) / 2)); } } + + if (buttons_container->get_child_count() > 0) { + buttons_container->reset_size(); + int x = is_layout_rtl() ? 0 : get_size().width - buttons_container->get_size().x; + int y = (header_height - buttons_container->get_size().y) / 2; + if (get_popup()) { + int menu_width = theme_cache.menu_icon->get_width() + theme_cache.buttons_separation; + x += is_layout_rtl() ? menu_width : -menu_width; + } + buttons_container->set_position(Point2(x, y)); + } } break; case NOTIFICATION_TRANSLATION_CHANGED: @@ -216,6 +228,8 @@ void TabContainer::_on_theme_changed() { 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); + buttons_container->add_theme_constant_override(SNAME("separation"), theme_cache.buttons_separation); + _update_margins(); if (get_tab_count() > 0) { _repaint(); @@ -259,10 +273,20 @@ void TabContainer::_update_margins() { // Directly check for validity, to avoid errors when quitting. bool has_popup = popup_obj_id.is_valid(); + bool has_buttons = buttons_container->get_child_count() > 0; + + int buttons_offset = has_popup ? menu_width : 0; + if (has_buttons) { + buttons_container->reset_size(); + buttons_offset += buttons_container->get_size().x + theme_cache.buttons_separation; + if (has_popup) { + buttons_offset += theme_cache.buttons_separation; + } + } if (get_tab_count() == 0) { tab_bar->set_offset(SIDE_LEFT, 0); - tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + tab_bar->set_offset(SIDE_RIGHT, -buttons_offset); return; } @@ -270,19 +294,19 @@ void TabContainer::_update_margins() { switch (get_tab_alignment()) { case TabBar::ALIGNMENT_LEFT: { tab_bar->set_offset(SIDE_LEFT, theme_cache.side_margin); - tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + tab_bar->set_offset(SIDE_RIGHT, -buttons_offset); } break; case TabBar::ALIGNMENT_CENTER: { tab_bar->set_offset(SIDE_LEFT, 0); - tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + tab_bar->set_offset(SIDE_RIGHT, -buttons_offset); } break; case TabBar::ALIGNMENT_RIGHT: { tab_bar->set_offset(SIDE_LEFT, 0); - if (has_popup) { - tab_bar->set_offset(SIDE_RIGHT, -menu_width); + if (has_popup || has_buttons) { + tab_bar->set_offset(SIDE_RIGHT, -buttons_offset); return; } @@ -292,7 +316,7 @@ void TabContainer::_update_margins() { // Calculate if all the tabs would still fit if the margin was present. if (get_clip_tabs() && (tab_bar->get_offset_buttons_visible() || (get_tab_count() > 1 && (total_tabs_width + theme_cache.side_margin) > get_size().width))) { - tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + tab_bar->set_offset(SIDE_RIGHT, -buttons_offset); } else { tab_bar->set_offset(SIDE_RIGHT, -theme_cache.side_margin); } @@ -314,7 +338,7 @@ Vector TabContainer::_get_tab_controls() const { Vector controls; for (int i = 0; i < get_child_count(); i++) { Control *control = Object::cast_to(get_child(i)); - if (!control || control->is_set_as_top_level() || control == tab_bar || children_removing.has(control)) { + if (!control || control->is_set_as_top_level() || control == tab_bar || control == buttons_container || children_removing.has(control)) { continue; } @@ -500,10 +524,18 @@ void TabContainer::_refresh_tab_names() { } } +void TabContainer::_on_buttons_container_child_order_changed() { + queue_redraw(); + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); + } +} + void TabContainer::add_child_notify(Node *p_child) { Container::add_child_notify(p_child); - if (p_child == tab_bar) { + if (p_child == tab_bar || p_child == buttons_container) { return; } @@ -531,7 +563,7 @@ void TabContainer::add_child_notify(Node *p_child) { void TabContainer::move_child_notify(Node *p_child) { Container::move_child_notify(p_child); - if (p_child == tab_bar) { + if (p_child == tab_bar || p_child == buttons_container) { return; } @@ -555,7 +587,7 @@ void TabContainer::move_child_notify(Node *p_child) { void TabContainer::remove_child_notify(Node *p_child) { Container::remove_child_notify(p_child); - if (p_child == tab_bar) { + if (p_child == tab_bar || p_child == buttons_container) { return; } @@ -804,6 +836,10 @@ Size2 TabContainer::get_minimum_size() const { ms.x += theme_cache.menu_icon->get_width(); } + if (buttons_container->get_child_count() > 0) { + ms.x += buttons_container->get_size().x; + } + if (theme_cache.side_margin > 0 && get_tab_alignment() != TabBar::ALIGNMENT_CENTER && (get_tab_alignment() != TabBar::ALIGNMENT_RIGHT || !get_popup())) { ms.x += theme_cache.side_margin; @@ -869,6 +905,30 @@ Popup *TabContainer::get_popup() const { return nullptr; } +void TabContainer::add_button(Control *p_button) { + buttons_container->add_child(p_button, false); + + queue_redraw(); + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); + } +} + +void TabContainer::remove_button(Control *p_button) { + if (p_button && p_button->get_parent() != buttons_container) { + return; + } + + buttons_container->remove_child(p_button); + + queue_redraw(); + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); + } +} + void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) { drag_to_rearrange_enabled = p_enabled; } @@ -937,6 +997,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("add_button", "button"), &TabContainer::add_button); + ClassDB::bind_method(D_METHOD("remove_button", "button"), &TabContainer::remove_button); 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); @@ -962,6 +1024,7 @@ void TabContainer::_bind_methods() { 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"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, buttons_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"); @@ -1007,5 +1070,9 @@ TabContainer::TabContainer() { tab_bar->connect("tab_selected", callable_mp(this, &TabContainer::_on_tab_selected)); tab_bar->connect("tab_button_pressed", callable_mp(this, &TabContainer::_on_tab_button_pressed)); + buttons_container = memnew(HBoxContainer); + add_child(buttons_container, false, INTERNAL_MODE_BACK); + buttons_container->connect("child_order_changed", callable_mp(this, &TabContainer::_on_buttons_container_child_order_changed)); + connect("mouse_exited", callable_mp(this, &TabContainer::_on_mouse_exited)); } diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 2bcc640d0566..39f85285a3ff 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -35,6 +35,9 @@ #include "scene/gui/popup.h" #include "scene/gui/tab_bar.h" +class Button; +class HBoxContainer; + class TabContainer : public Container { GDCLASS(TabContainer, Container); @@ -43,6 +46,7 @@ class TabContainer : public Container { bool all_tabs_in_front = false; bool menu_hovered = false; mutable ObjectID popup_obj_id; + HBoxContainer *buttons_container = nullptr; bool drag_to_rearrange_enabled = false; bool use_hidden_tabs_for_min_size = false; bool theme_changing = false; @@ -50,6 +54,7 @@ class TabContainer : public Container { struct ThemeCache { int side_margin = 0; + int buttons_separation = 4; Ref panel_style; Ref tabbar_style; @@ -97,6 +102,8 @@ class TabContainer : public Container { void _on_tab_selected(int p_tab); void _on_tab_button_pressed(int p_tab); + void _on_buttons_container_child_order_changed(); + Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control); bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const; void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control); @@ -157,6 +164,9 @@ class TabContainer : public Container { void set_popup(Node *p_popup); Popup *get_popup() const; + void add_button(Control *p_button); + void remove_button(Control *p_button); + 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 7efbc74bf38b..7ec4798b5575 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -872,6 +872,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_color("drop_mark_color", "TabContainer", Color(1, 1, 1)); theme->set_constant("side_margin", "TabContainer", Math::round(8 * scale)); + theme->set_constant("buttons_separation", "TabContainer", Math::round(4 * scale)); theme->set_constant("icon_separation", "TabContainer", Math::round(4 * scale)); theme->set_constant("icon_max_width", "TabContainer", 0); theme->set_constant("outline_size", "TabContainer", 0);