Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding buttons to the TabContainer header #80301

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/classes/TabContainer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
<link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link>
</tutorials>
<methods>
<method name="add_button">
<return type="void" />
<param index="0" name="button" type="Control" />
<description>
Add a [Button] to the tabs header.
</description>
</method>
<method name="get_current_tab_control" qualifiers="const">
<return type="Control" />
<description>
Expand Down Expand Up @@ -99,6 +106,13 @@
Returns [code]true[/code] if the tab at index [param tab_idx] is hidden.
</description>
</method>
<method name="remove_button">
<return type="void" />
<param index="0" name="button" type="Control" />
<description>
Remove a [Button] from the tabs header.
</description>
</method>
<method name="set_popup">
<return type="void" />
<param index="0" name="popup" type="Node" />
Expand Down Expand Up @@ -244,6 +258,9 @@
<theme_item name="font_unselected_color" data_type="color" type="Color" default="Color(0.7, 0.7, 0.7, 1)">
Font color of the other, unselected tabs.
</theme_item>
<theme_item name="buttons_separation" data_type="constant" type="int" default="4">
The size of the space between each of the buttons in the [TabBar].
</theme_item>
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
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.
</theme_item>
Expand Down
87 changes: 77 additions & 10 deletions scene/gui/tab_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -259,30 +273,40 @@ 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;
}

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;
}

Expand All @@ -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);
}
Expand All @@ -314,7 +338,7 @@ Vector<Control *> TabContainer::_get_tab_controls() const {
Vector<Control *> controls;
for (int i = 0; i < get_child_count(); i++) {
Control *control = Object::cast_to<Control>(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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand Down Expand Up @@ -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));
}
10 changes: 10 additions & 0 deletions scene/gui/tab_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -43,13 +46,15 @@ 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;
Vector<Control *> children_removing;

struct ThemeCache {
int side_margin = 0;
int buttons_separation = 4;

Ref<StyleBox> panel_style;
Ref<StyleBox> tabbar_style;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions scene/theme/default_theme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &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);
Expand Down