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 no tabs to be selected in TabBar and TabContainer #87194

Merged
merged 1 commit into from
Jan 30, 2024
Merged
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
7 changes: 5 additions & 2 deletions doc/classes/TabBar.xml
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,11 @@
<member name="clip_tabs" type="bool" setter="set_clip_tabs" getter="get_clip_tabs" default="true">
If [code]true[/code], tabs overflowing this node's width will be hidden, displaying two navigation buttons instead. Otherwise, this node's minimum size is updated so that all tabs are visible.
</member>
<member name="current_tab" type="int" setter="set_current_tab" getter="get_current_tab" default="0">
Select tab at index [code]tab_idx[/code].
<member name="current_tab" type="int" setter="set_current_tab" getter="get_current_tab" default="-1">
The index of the current selected tab. A value of [code]-1[/code] means that no tab is selected and can only be set when [member deselect_enabled] is [code]true[/code] or if all tabs are hidden or disabled.
</member>
<member name="deselect_enabled" type="bool" setter="set_deselect_enabled" getter="get_deselect_enabled" default="false">
If [code]true[/code], all tabs can be deselected so that no tab is selected. Click on the current tab to deselect it.
</member>
<member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled" default="false">
If [code]true[/code], tabs can be rearranged with mouse drag.
Expand Down
7 changes: 6 additions & 1 deletion doc/classes/TabContainer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,13 @@
<member name="clip_tabs" type="bool" setter="set_clip_tabs" getter="get_clip_tabs" default="true">
If [code]true[/code], tabs overflowing this node's width will be hidden, displaying two navigation buttons instead. Otherwise, this node's minimum size is updated so that all tabs are visible.
</member>
<member name="current_tab" type="int" setter="set_current_tab" getter="get_current_tab" default="0">
<member name="current_tab" type="int" setter="set_current_tab" getter="get_current_tab" default="-1">
The current tab index. When set, this index's [Control] node's [code]visible[/code] property is set to [code]true[/code] and all others are set to [code]false[/code].
A value of [code]-1[/code] means that no tab is selected.
</member>
<member name="deselect_enabled" type="bool" setter="set_deselect_enabled" getter="get_deselect_enabled" default="false">
If [code]true[/code], all tabs can be deselected so that no tab is selected. Click on the [member current_tab] to deselect it.
Only the tab header will be shown if no tabs are selected.
</member>
<member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled" default="false">
If [code]true[/code], tabs can be rearranged with mouse drag.
Expand Down
110 changes: 79 additions & 31 deletions scene/gui/tab_bar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,11 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}

if (found != -1) {
set_current_tab(found);
if (deselect_enabled && get_current_tab() == found) {
set_current_tab(-1);
} else {
set_current_tab(found);
}

if (mb->get_button_index() == MouseButton::RIGHT) {
// Right mouse button clicked.
Expand Down Expand Up @@ -616,8 +620,8 @@ void TabBar::set_tab_count(int p_count) {
if (p_count == 0) {
offset = 0;
max_drawn_tab = 0;
current = 0;
previous = 0;
current = -1;
previous = -1;
} else {
offset = MIN(offset, p_count - 1);
max_drawn_tab = MIN(max_drawn_tab, p_count - 1);
Expand All @@ -640,7 +644,12 @@ int TabBar::get_tab_count() const {
}

void TabBar::set_current_tab(int p_current) {
ERR_FAIL_INDEX(p_current, get_tab_count());
if (p_current == -1) {
// An index of -1 is only valid if deselecting is enabled or there are no valid tabs.
ERR_FAIL_COND_MSG(!_can_deselect(), "Cannot deselect tabs, deselection is not enabled.");
} else {
ERR_FAIL_INDEX(p_current, get_tab_count());
}

previous = current;
current = p_current;
Expand Down Expand Up @@ -1069,8 +1078,13 @@ void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
queue_redraw();
update_minimum_size();

if (tabs.size() == 1 && is_inside_tree()) {
emit_signal(SNAME("tab_changed"), 0);
if (tabs.size() == 1) {
if (is_inside_tree()) {
set_current_tab(0);
} else {
current = 0;
previous = -1;
}
}
}

Expand All @@ -1082,8 +1096,8 @@ void TabBar::clear_tabs() {
tabs.clear();
offset = 0;
max_drawn_tab = 0;
current = 0;
previous = 0;
current = -1;
previous = -1;

queue_redraw();
update_minimum_size();
Expand All @@ -1094,34 +1108,43 @@ void TabBar::remove_tab(int p_idx) {
ERR_FAIL_INDEX(p_idx, tabs.size());
tabs.remove_at(p_idx);

bool is_tab_changing = current == p_idx && !tabs.is_empty();
bool is_tab_changing = current == p_idx;

if (current >= p_idx && current > 0) {
current--;
}
if (previous >= p_idx && previous > 0) {
previous--;
}

if (tabs.is_empty()) {
offset = 0;
max_drawn_tab = 0;
previous = 0;
current = -1;
previous = -1;
} else {
// Try to change to a valid tab if possible (without firing the `tab_selected` signal).
for (int i = current; i < tabs.size(); i++) {
if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
current = i;
break;
}
}
// If nothing, try backwards.
if (is_tab_disabled(current) || is_tab_hidden(current)) {
for (int i = current - 1; i >= 0; i--) {
if (current != -1) {
// Try to change to a valid tab if possible (without firing the `tab_selected` signal).
for (int i = current; i < tabs.size(); i++) {
if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
current = i;
break;
}
}
// If nothing, try backwards.
if (is_tab_disabled(current) || is_tab_hidden(current)) {
for (int i = current - 1; i >= 0; i--) {
if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
current = i;
break;
}
}
}
// If still no valid tab, deselect.
if (is_tab_disabled(current) || is_tab_hidden(current)) {
current = -1;
}
}

offset = MIN(offset, tabs.size() - 1);
max_drawn_tab = MIN(max_drawn_tab, tabs.size() - 1);

Expand Down Expand Up @@ -1301,17 +1324,9 @@ void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_in

if (!is_tab_disabled(p_to_index)) {
set_current_tab(p_to_index);
if (tabs.size() == 1) {
_update_cache();
queue_redraw();
emit_signal(SNAME("tab_changed"), 0);
}
} else {
_update_cache();
queue_redraw();
if (tabs.size() == 1) {
emit_signal(SNAME("tab_changed"), 0);
}
}

update_minimum_size();
Expand Down Expand Up @@ -1398,9 +1413,9 @@ void TabBar::move_tab(int p_from, int p_to) {

if (previous == p_from) {
previous = p_to;
} else if (previous > p_from && previous >= p_to) {
} else if (previous > p_from && previous <= p_to) {
previous--;
} else if (previous < p_from && previous <= p_to) {
} else if (previous < p_from && previous >= p_to) {
previous++;
}

Expand Down Expand Up @@ -1516,10 +1531,26 @@ void TabBar::_ensure_no_over_offset() {
}
}

bool TabBar::_can_deselect() const {
if (deselect_enabled) {
return true;
}
// All tabs must be disabled or hidden.
for (const Tab &tab : tabs) {
if (!tab.disabled && !tab.hidden) {
return false;
}
}
return true;
}

void TabBar::ensure_tab_visible(int p_idx) {
if (!is_inside_tree() || !buttons_visible) {
return;
}
if (p_idx == -1 && _can_deselect()) {
return;
}
ERR_FAIL_INDEX(p_idx, tabs.size());

if (tabs[p_idx].hidden || (p_idx >= offset && p_idx <= max_drawn_tab)) {
Expand Down Expand Up @@ -1662,6 +1693,20 @@ bool TabBar::get_select_with_rmb() const {
return select_with_rmb;
}

void TabBar::set_deselect_enabled(bool p_enabled) {
if (deselect_enabled == p_enabled) {
return;
}
deselect_enabled = p_enabled;
if (!deselect_enabled && current == -1 && !tabs.is_empty()) {
select_next_available();
YuriSizov marked this conversation as resolved.
Show resolved Hide resolved
}
}

bool TabBar::get_deselect_enabled() const {
return deselect_enabled;
}

bool TabBar::_set(const StringName &p_name, const Variant &p_value) {
Vector<String> components = String(p_name).split("/", true, 2);
if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) {
Expand Down Expand Up @@ -1766,6 +1811,8 @@ void TabBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_scroll_to_selected"), &TabBar::get_scroll_to_selected);
ClassDB::bind_method(D_METHOD("set_select_with_rmb", "enabled"), &TabBar::set_select_with_rmb);
ClassDB::bind_method(D_METHOD("get_select_with_rmb"), &TabBar::get_select_with_rmb);
ClassDB::bind_method(D_METHOD("set_deselect_enabled", "enabled"), &TabBar::set_deselect_enabled);
ClassDB::bind_method(D_METHOD("get_deselect_enabled"), &TabBar::get_deselect_enabled);
ClassDB::bind_method(D_METHOD("clear_tabs"), &TabBar::clear_tabs);

ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab")));
Expand All @@ -1790,6 +1837,7 @@ void TabBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_enabled"), "set_deselect_enabled", "get_deselect_enabled");

BIND_ENUM_CONSTANT(ALIGNMENT_LEFT);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
Expand Down
9 changes: 7 additions & 2 deletions scene/gui/tab_bar.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,16 @@ class TabBar : public Control {
bool buttons_visible = false;
bool missing_right = false;
Vector<Tab> tabs;
int current = 0;
int previous = 0;
int current = -1;
int previous = -1;
AlignmentMode tab_alignment = ALIGNMENT_LEFT;
bool clip_tabs = true;
int rb_hover = -1;
bool rb_pressing = false;
bool tab_style_v_flip = false;

bool select_with_rmb = false;
bool deselect_enabled = false;

int cb_hover = -1;
bool cb_pressing = false;
Expand Down Expand Up @@ -146,6 +147,7 @@ class TabBar : public Control {
int get_tab_width(int p_idx) const;
Size2 _get_tab_icon_size(int p_idx) const;
void _ensure_no_over_offset();
bool _can_deselect() const;

void _update_hover();
void _update_cache(bool p_update_hover = true);
Expand Down Expand Up @@ -250,6 +252,9 @@ class TabBar : public Control {
void set_select_with_rmb(bool p_enabled);
bool get_select_with_rmb() const;

void set_deselect_enabled(bool p_enabled);
bool get_deselect_enabled() const;

void ensure_tab_visible(int p_idx);

void set_max_tab_width(int p_width);
Expand Down
Loading
Loading