瀏覽代碼

Allow to focus individual tabs in TabBar/TabContainer

Arkadiusz Marcin Kołek 2 年之前
父節點
當前提交
18811ac814

+ 16 - 0
doc/classes/TabBar.xml

@@ -140,6 +140,18 @@
 				Removes the tab at index [param tab_idx].
 				Removes the tab at index [param tab_idx].
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="select_next_available">
+			<return type="bool" />
+			<description>
+				Selects the first available tab with greater index than the currently selected. Returns [code]true[/code] if tab selection changed.
+			</description>
+		</method>
+		<method name="select_previous_available">
+			<return type="bool" />
+			<description>
+				Selects the first available tab with lower index than the currently selected. Returns [code]true[/code] if tab selection changed.
+			</description>
+		</method>
 		<method name="set_tab_button_icon">
 		<method name="set_tab_button_icon">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="tab_idx" type="int" />
 			<param index="0" name="tab_idx" type="int" />
@@ -223,6 +235,7 @@
 		<member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled" default="false">
 		<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.
 			If [code]true[/code], tabs can be rearranged with mouse drag.
 		</member>
 		</member>
+		<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="2" />
 		<member name="max_tab_width" type="int" setter="set_max_tab_width" getter="get_max_tab_width" default="0">
 		<member name="max_tab_width" type="int" setter="set_max_tab_width" getter="get_max_tab_width" default="0">
 			Sets the maximum width which all tabs should be limited to. Unlimited if set to [code]0[/code].
 			Sets the maximum width which all tabs should be limited to. Unlimited if set to [code]0[/code].
 		</member>
 		</member>
@@ -396,6 +409,9 @@
 		<theme_item name="tab_disabled" data_type="style" type="StyleBox">
 		<theme_item name="tab_disabled" data_type="style" type="StyleBox">
 			The style of disabled tabs.
 			The style of disabled tabs.
 		</theme_item>
 		</theme_item>
+		<theme_item name="tab_focus" data_type="style" type="StyleBox">
+			[StyleBox] used when the [TabBar] is focused. The [theme_item tab_focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox] of the selected tab, so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+		</theme_item>
 		<theme_item name="tab_hovered" data_type="style" type="StyleBox">
 		<theme_item name="tab_hovered" data_type="style" type="StyleBox">
 			The style of the currently hovered tab. Does not apply to the selected tab.
 			The style of the currently hovered tab. Does not apply to the selected tab.
 		</theme_item>
 		</theme_item>

+ 18 - 0
doc/classes/TabContainer.xml

@@ -99,6 +99,18 @@
 				Returns [code]true[/code] if the tab at index [param tab_idx] is hidden.
 				Returns [code]true[/code] if the tab at index [param tab_idx] is hidden.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="select_next_available">
+			<return type="bool" />
+			<description>
+				Selects the first available tab with greater index than the currently selected. Returns [code]true[/code] if tab selection changed.
+			</description>
+		</method>
+		<method name="select_previous_available">
+			<return type="bool" />
+			<description>
+				Selects the first available tab with lower index than the currently selected. Returns [code]true[/code] if tab selection changed.
+			</description>
+		</method>
 		<method name="set_popup">
 		<method name="set_popup">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="popup" type="Node" />
 			<param index="0" name="popup" type="Node" />
@@ -171,6 +183,9 @@
 		<member name="tab_alignment" type="int" setter="set_tab_alignment" getter="get_tab_alignment" enum="TabBar.AlignmentMode" default="0">
 		<member name="tab_alignment" type="int" setter="set_tab_alignment" getter="get_tab_alignment" enum="TabBar.AlignmentMode" default="0">
 			Sets the position at which tabs will be placed. See [enum TabBar.AlignmentMode] for details.
 			Sets the position at which tabs will be placed. See [enum TabBar.AlignmentMode] for details.
 		</member>
 		</member>
+		<member name="tab_focus_mode" type="int" setter="set_tab_focus_mode" getter="get_tab_focus_mode" enum="Control.FocusMode" default="2">
+			The focus access mode for the internal [TabBar] node.
+		</member>
 		<member name="tabs_rearrange_group" type="int" setter="set_tabs_rearrange_group" getter="get_tabs_rearrange_group" default="-1">
 		<member name="tabs_rearrange_group" type="int" setter="set_tabs_rearrange_group" getter="get_tabs_rearrange_group" default="-1">
 			[TabContainer]s with the same rearrange group ID will allow dragging the tabs between them. Enable drag with [member drag_to_rearrange_enabled].
 			[TabContainer]s with the same rearrange group ID will allow dragging the tabs between them. Enable drag with [member drag_to_rearrange_enabled].
 			Setting this to [code]-1[/code] will disable rearranging between [TabContainer]s.
 			Setting this to [code]-1[/code] will disable rearranging between [TabContainer]s.
@@ -291,6 +306,9 @@
 		<theme_item name="tab_disabled" data_type="style" type="StyleBox">
 		<theme_item name="tab_disabled" data_type="style" type="StyleBox">
 			The style of disabled tabs.
 			The style of disabled tabs.
 		</theme_item>
 		</theme_item>
+		<theme_item name="tab_focus" data_type="style" type="StyleBox">
+			[StyleBox] used when the [TabBar] is focused. The [theme_item tab_focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox] of the selected tab, so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+		</theme_item>
 		<theme_item name="tab_hovered" data_type="style" type="StyleBox">
 		<theme_item name="tab_hovered" data_type="style" type="StyleBox">
 			The style of the currently hovered tab.
 			The style of the currently hovered tab.
 		</theme_item>
 		</theme_item>

+ 4 - 0
editor/editor_themes.cpp

@@ -913,6 +913,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	style_tab_disabled->set_bg_color(disabled_bg_color);
 	style_tab_disabled->set_bg_color(disabled_bg_color);
 	style_tab_disabled->set_border_color(disabled_bg_color);
 	style_tab_disabled->set_border_color(disabled_bg_color);
 
 
+	Ref<StyleBoxFlat> style_tab_focus = style_widget_focus->duplicate();
+
 	// Editor background
 	// Editor background
 	Color background_color_opaque = background_color;
 	Color background_color_opaque = background_color;
 	background_color_opaque.a = 1.0;
 	background_color_opaque.a = 1.0;
@@ -1536,10 +1538,12 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	theme->set_stylebox("tab_hovered", "TabContainer", style_tab_hovered);
 	theme->set_stylebox("tab_hovered", "TabContainer", style_tab_hovered);
 	theme->set_stylebox("tab_unselected", "TabContainer", style_tab_unselected);
 	theme->set_stylebox("tab_unselected", "TabContainer", style_tab_unselected);
 	theme->set_stylebox("tab_disabled", "TabContainer", style_tab_disabled);
 	theme->set_stylebox("tab_disabled", "TabContainer", style_tab_disabled);
+	theme->set_stylebox("tab_focus", "TabContainer", style_tab_focus);
 	theme->set_stylebox("tab_selected", "TabBar", style_tab_selected);
 	theme->set_stylebox("tab_selected", "TabBar", style_tab_selected);
 	theme->set_stylebox("tab_hovered", "TabBar", style_tab_hovered);
 	theme->set_stylebox("tab_hovered", "TabBar", style_tab_hovered);
 	theme->set_stylebox("tab_unselected", "TabBar", style_tab_unselected);
 	theme->set_stylebox("tab_unselected", "TabBar", style_tab_unselected);
 	theme->set_stylebox("tab_disabled", "TabBar", style_tab_disabled);
 	theme->set_stylebox("tab_disabled", "TabBar", style_tab_disabled);
+	theme->set_stylebox("tab_focus", "TabBar", style_tab_focus);
 	theme->set_stylebox("button_pressed", "TabBar", style_menu);
 	theme->set_stylebox("button_pressed", "TabBar", style_menu);
 	theme->set_stylebox("button_highlight", "TabBar", style_menu);
 	theme->set_stylebox("button_highlight", "TabBar", style_menu);
 	theme->set_color("font_selected_color", "TabContainer", font_color);
 	theme->set_color("font_selected_color", "TabContainer", font_color);

+ 88 - 3
scene/gui/tab_bar.cpp

@@ -290,6 +290,34 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
 			}
 			}
 		}
 		}
 	}
 	}
+
+	if (p_event->is_pressed()) {
+		Input *input = Input::get_singleton();
+		Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
+		Ref<InputEventJoypadButton> joypadbutton_event = p_event;
+		bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid());
+		if (p_event->is_action("ui_right", true)) {
+			if (is_joypad_event) {
+				if (!input->is_action_just_pressed("ui_right", true)) {
+					return;
+				}
+				set_process_internal(true);
+			}
+			if (is_layout_rtl() ? select_previous_available() : select_next_available()) {
+				accept_event();
+			}
+		} else if (p_event->is_action("ui_left", true)) {
+			if (is_joypad_event) {
+				if (!input->is_action_just_pressed("ui_left", true)) {
+					return;
+				}
+				set_process_internal(true);
+			}
+			if (is_layout_rtl() ? select_next_available() : select_previous_available()) {
+				accept_event();
+			}
+		}
+	}
 }
 }
 
 
 void TabBar::_shape(int p_tab) {
 void TabBar::_shape(int p_tab) {
@@ -307,6 +335,28 @@ void TabBar::_shape(int p_tab) {
 
 
 void TabBar::_notification(int p_what) {
 void TabBar::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
+		case NOTIFICATION_INTERNAL_PROCESS: {
+			Input *input = Input::get_singleton();
+
+			if (input->is_action_just_released("ui_left") || input->is_action_just_released("ui_right")) {
+				gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
+				set_process_internal(false);
+				return;
+			}
+
+			gamepad_event_delay_ms -= get_process_delta_time();
+			if (gamepad_event_delay_ms <= 0) {
+				gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
+				if (input->is_action_pressed("ui_right")) {
+					is_layout_rtl() ? select_previous_available() : select_next_available();
+				}
+
+				if (input->is_action_pressed("ui_left")) {
+					is_layout_rtl() ? select_next_available() : select_previous_available();
+				}
+			}
+		} break;
+
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			queue_redraw();
 			queue_redraw();
 		} break;
 		} break;
@@ -379,7 +429,7 @@ void TabBar::_notification(int p_what) {
 						col = theme_cache.font_unselected_color;
 						col = theme_cache.font_unselected_color;
 					}
 					}
 
 
-					_draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs);
+					_draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs, false);
 				}
 				}
 
 
 				ofs += tabs[i].size_cache;
 				ofs += tabs[i].size_cache;
@@ -390,7 +440,7 @@ void TabBar::_notification(int p_what) {
 				Ref<StyleBox> sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style;
 				Ref<StyleBox> 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 - tabs[current].size_cache : tabs[current].ofs_cache;
 
 
-				_draw_tab(sb, theme_cache.font_selected_color, current, x);
+				_draw_tab(sb, theme_cache.font_selected_color, current, x, has_focus());
 			}
 			}
 
 
 			if (buttons_visible) {
 			if (buttons_visible) {
@@ -456,12 +506,16 @@ void TabBar::_notification(int p_what) {
 	}
 	}
 }
 }
 
 
-void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) {
+void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x, bool p_focus) {
 	RID ci = get_canvas_item();
 	RID ci = get_canvas_item();
 	bool rtl = is_layout_rtl();
 	bool rtl = is_layout_rtl();
 
 
 	Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height);
 	Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height);
 	p_tab_style->draw(ci, sb_rect);
 	p_tab_style->draw(ci, sb_rect);
+	if (p_focus) {
+		Ref<StyleBox> focus_style = theme_cache.tab_focus_style;
+		focus_style->draw(ci, sb_rect);
+	}
 
 
 	p_x += rtl ? tabs[p_index].size_cache - p_tab_style->get_margin(SIDE_LEFT) : p_tab_style->get_margin(SIDE_LEFT);
 	p_x += rtl ? tabs[p_index].size_cache - p_tab_style->get_margin(SIDE_LEFT) : p_tab_style->get_margin(SIDE_LEFT);
 
 
@@ -607,6 +661,33 @@ int TabBar::get_hovered_tab() const {
 	return hover;
 	return hover;
 }
 }
 
 
+bool TabBar::select_previous_available() {
+	const int offset_end = (get_current_tab() + 1);
+	for (int i = 1; i < offset_end; i++) {
+		int target_tab = get_current_tab() - i;
+		if (target_tab < 0) {
+			target_tab += get_tab_count();
+		}
+		if (!is_tab_disabled(target_tab)) {
+			set_current_tab(target_tab);
+			return true;
+		}
+	}
+	return false;
+}
+
+bool TabBar::select_next_available() {
+	const int offset_end = (get_tab_count() - get_current_tab());
+	for (int i = 1; i < offset_end; i++) {
+		int target_tab = (get_current_tab() + i) % get_tab_count();
+		if (!is_tab_disabled(target_tab)) {
+			set_current_tab(target_tab);
+			return true;
+		}
+	}
+	return false;
+}
+
 int TabBar::get_tab_offset() const {
 int TabBar::get_tab_offset() const {
 	return offset;
 	return offset;
 }
 }
@@ -1589,6 +1670,8 @@ void TabBar::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabBar::set_current_tab);
 	ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabBar::set_current_tab);
 	ClassDB::bind_method(D_METHOD("get_current_tab"), &TabBar::get_current_tab);
 	ClassDB::bind_method(D_METHOD("get_current_tab"), &TabBar::get_current_tab);
 	ClassDB::bind_method(D_METHOD("get_previous_tab"), &TabBar::get_previous_tab);
 	ClassDB::bind_method(D_METHOD("get_previous_tab"), &TabBar::get_previous_tab);
+	ClassDB::bind_method(D_METHOD("select_previous_available"), &TabBar::select_previous_available);
+	ClassDB::bind_method(D_METHOD("select_next_available"), &TabBar::select_next_available);
 	ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabBar::set_tab_title);
 	ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabBar::set_tab_title);
 	ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title);
 	ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title);
 	ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction);
 	ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction);
@@ -1674,6 +1757,7 @@ void TabBar::_bind_methods() {
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_hovered_style, "tab_hovered");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_hovered_style, "tab_hovered");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_selected_style, "tab_selected");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_selected_style, "tab_selected");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_disabled_style, "tab_disabled");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_disabled_style, "tab_disabled");
+	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, tab_focus_style, "tab_focus");
 
 
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, increment_icon, "increment");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, increment_icon, "increment");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, increment_hl_icon, "increment_highlight");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, increment_hl_icon, "increment_highlight");
@@ -1699,5 +1783,6 @@ void TabBar::_bind_methods() {
 
 
 TabBar::TabBar() {
 TabBar::TabBar() {
 	set_size(Size2(get_size().width, get_minimum_size().height));
 	set_size(Size2(get_size().width, get_minimum_size().height));
+	set_focus_mode(FOCUS_ALL);
 	connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited));
 	connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited));
 }
 }

+ 9 - 1
scene/gui/tab_bar.h

@@ -107,6 +107,10 @@ private:
 	bool scroll_to_selected = true;
 	bool scroll_to_selected = true;
 	int tabs_rearrange_group = -1;
 	int tabs_rearrange_group = -1;
 
 
+	const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
+	const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
+	float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
+
 	struct ThemeCache {
 	struct ThemeCache {
 		int h_separation = 0;
 		int h_separation = 0;
 		int icon_max_width = 0;
 		int icon_max_width = 0;
@@ -115,6 +119,7 @@ private:
 		Ref<StyleBox> tab_hovered_style;
 		Ref<StyleBox> tab_hovered_style;
 		Ref<StyleBox> tab_selected_style;
 		Ref<StyleBox> tab_selected_style;
 		Ref<StyleBox> tab_disabled_style;
 		Ref<StyleBox> tab_disabled_style;
+		Ref<StyleBox> tab_focus_style;
 
 
 		Ref<Texture2D> increment_icon;
 		Ref<Texture2D> increment_icon;
 		Ref<Texture2D> increment_hl_icon;
 		Ref<Texture2D> increment_hl_icon;
@@ -148,7 +153,7 @@ private:
 	void _on_mouse_exited();
 	void _on_mouse_exited();
 
 
 	void _shape(int p_tab);
 	void _shape(int p_tab);
-	void _draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x);
+	void _draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x, bool p_focus);
 
 
 protected:
 protected:
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
@@ -214,6 +219,9 @@ public:
 	int get_previous_tab() const;
 	int get_previous_tab() const;
 	int get_hovered_tab() const;
 	int get_hovered_tab() const;
 
 
+	bool select_previous_available();
+	bool select_next_available();
+
 	int get_tab_offset() const;
 	int get_tab_offset() const;
 	bool get_offset_buttons_visible() const;
 	bool get_offset_buttons_visible() const;
 
 

+ 23 - 0
scene/gui/tab_container.cpp

@@ -195,6 +195,7 @@ void TabContainer::_on_theme_changed() {
 	tab_bar->add_theme_style_override(SNAME("tab_hovered"), theme_cache.tab_hovered_style);
 	tab_bar->add_theme_style_override(SNAME("tab_hovered"), theme_cache.tab_hovered_style);
 	tab_bar->add_theme_style_override(SNAME("tab_selected"), theme_cache.tab_selected_style);
 	tab_bar->add_theme_style_override(SNAME("tab_selected"), theme_cache.tab_selected_style);
 	tab_bar->add_theme_style_override(SNAME("tab_disabled"), theme_cache.tab_disabled_style);
 	tab_bar->add_theme_style_override(SNAME("tab_disabled"), theme_cache.tab_disabled_style);
+	tab_bar->add_theme_style_override(SNAME("tab_focus"), theme_cache.tab_focus_style);
 
 
 	tab_bar->add_theme_icon_override(SNAME("increment"), theme_cache.increment_icon);
 	tab_bar->add_theme_icon_override(SNAME("increment"), theme_cache.increment_icon);
 	tab_bar->add_theme_icon_override(SNAME("increment_highlight"), theme_cache.increment_hl_icon);
 	tab_bar->add_theme_icon_override(SNAME("increment_highlight"), theme_cache.increment_hl_icon);
@@ -601,6 +602,14 @@ int TabContainer::get_previous_tab() const {
 	return tab_bar->get_previous_tab();
 	return tab_bar->get_previous_tab();
 }
 }
 
 
+bool TabContainer::select_previous_available() {
+	return tab_bar->select_previous_available();
+}
+
+bool TabContainer::select_next_available() {
+	return tab_bar->select_next_available();
+}
+
 Control *TabContainer::get_tab_control(int p_idx) const {
 Control *TabContainer::get_tab_control(int p_idx) const {
 	Vector<Control *> controls = _get_tab_controls();
 	Vector<Control *> controls = _get_tab_controls();
 	if (p_idx >= 0 && p_idx < controls.size()) {
 	if (p_idx >= 0 && p_idx < controls.size()) {
@@ -645,6 +654,14 @@ TabBar::AlignmentMode TabContainer::get_tab_alignment() const {
 	return tab_bar->get_tab_alignment();
 	return tab_bar->get_tab_alignment();
 }
 }
 
 
+void TabContainer::set_tab_focus_mode(Control::FocusMode p_focus_mode) {
+	tab_bar->set_focus_mode(p_focus_mode);
+}
+
+Control::FocusMode TabContainer::get_tab_focus_mode() const {
+	return tab_bar->get_focus_mode();
+}
+
 void TabContainer::set_clip_tabs(bool p_clip_tabs) {
 void TabContainer::set_clip_tabs(bool p_clip_tabs) {
 	tab_bar->set_clip_tabs(p_clip_tabs);
 	tab_bar->set_clip_tabs(p_clip_tabs);
 }
 }
@@ -911,6 +928,8 @@ void TabContainer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabContainer::set_current_tab);
 	ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabContainer::set_current_tab);
 	ClassDB::bind_method(D_METHOD("get_current_tab"), &TabContainer::get_current_tab);
 	ClassDB::bind_method(D_METHOD("get_current_tab"), &TabContainer::get_current_tab);
 	ClassDB::bind_method(D_METHOD("get_previous_tab"), &TabContainer::get_previous_tab);
 	ClassDB::bind_method(D_METHOD("get_previous_tab"), &TabContainer::get_previous_tab);
+	ClassDB::bind_method(D_METHOD("select_previous_available"), &TabContainer::select_previous_available);
+	ClassDB::bind_method(D_METHOD("select_next_available"), &TabContainer::select_next_available);
 	ClassDB::bind_method(D_METHOD("get_current_tab_control"), &TabContainer::get_current_tab_control);
 	ClassDB::bind_method(D_METHOD("get_current_tab_control"), &TabContainer::get_current_tab_control);
 	ClassDB::bind_method(D_METHOD("get_tab_control", "tab_idx"), &TabContainer::get_tab_control);
 	ClassDB::bind_method(D_METHOD("get_tab_control", "tab_idx"), &TabContainer::get_tab_control);
 	ClassDB::bind_method(D_METHOD("set_tab_alignment", "alignment"), &TabContainer::set_tab_alignment);
 	ClassDB::bind_method(D_METHOD("set_tab_alignment", "alignment"), &TabContainer::set_tab_alignment);
@@ -943,6 +962,8 @@ void TabContainer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabContainer::get_tabs_rearrange_group);
 	ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabContainer::get_tabs_rearrange_group);
 	ClassDB::bind_method(D_METHOD("set_use_hidden_tabs_for_min_size", "enabled"), &TabContainer::set_use_hidden_tabs_for_min_size);
 	ClassDB::bind_method(D_METHOD("set_use_hidden_tabs_for_min_size", "enabled"), &TabContainer::set_use_hidden_tabs_for_min_size);
 	ClassDB::bind_method(D_METHOD("get_use_hidden_tabs_for_min_size"), &TabContainer::get_use_hidden_tabs_for_min_size);
 	ClassDB::bind_method(D_METHOD("get_use_hidden_tabs_for_min_size"), &TabContainer::get_use_hidden_tabs_for_min_size);
+	ClassDB::bind_method(D_METHOD("set_tab_focus_mode", "focus_mode"), &TabContainer::set_tab_focus_mode);
+	ClassDB::bind_method(D_METHOD("get_tab_focus_mode"), &TabContainer::get_tab_focus_mode);
 
 
 	ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to")));
 	ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to")));
 	ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab")));
 	ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab")));
@@ -960,6 +981,7 @@ void TabContainer::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group");
 	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");
 	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");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_tab_focus_mode", "get_tab_focus_mode");
 
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin);
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin);
 
 
@@ -977,6 +999,7 @@ void TabContainer::_bind_methods() {
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_hovered_style, "tab_hovered");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_hovered_style, "tab_hovered");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_selected_style, "tab_selected");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_selected_style, "tab_selected");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_disabled_style, "tab_disabled");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_disabled_style, "tab_disabled");
+	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, tab_focus_style, "tab_focus");
 
 
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabContainer, increment_icon, "increment");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabContainer, increment_icon, "increment");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabContainer, increment_hl_icon, "increment_highlight");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabContainer, increment_hl_icon, "increment_highlight");

+ 7 - 0
scene/gui/tab_container.h

@@ -66,6 +66,7 @@ class TabContainer : public Container {
 		Ref<StyleBox> tab_hovered_style;
 		Ref<StyleBox> tab_hovered_style;
 		Ref<StyleBox> tab_selected_style;
 		Ref<StyleBox> tab_selected_style;
 		Ref<StyleBox> tab_disabled_style;
 		Ref<StyleBox> tab_disabled_style;
+		Ref<StyleBox> tab_focus_style;
 
 
 		Ref<Texture2D> increment_icon;
 		Ref<Texture2D> increment_icon;
 		Ref<Texture2D> increment_hl_icon;
 		Ref<Texture2D> increment_hl_icon;
@@ -117,6 +118,9 @@ public:
 	void set_tab_alignment(TabBar::AlignmentMode p_alignment);
 	void set_tab_alignment(TabBar::AlignmentMode p_alignment);
 	TabBar::AlignmentMode get_tab_alignment() const;
 	TabBar::AlignmentMode get_tab_alignment() const;
 
 
+	void set_tab_focus_mode(FocusMode p_focus_mode);
+	FocusMode get_tab_focus_mode() const;
+
 	void set_clip_tabs(bool p_clip_tabs);
 	void set_clip_tabs(bool p_clip_tabs);
 	bool get_clip_tabs() const;
 	bool get_clip_tabs() const;
 
 
@@ -149,6 +153,9 @@ public:
 	int get_current_tab() const;
 	int get_current_tab() const;
 	int get_previous_tab() const;
 	int get_previous_tab() const;
 
 
+	bool select_previous_available();
+	bool select_next_available();
+
 	Control *get_tab_control(int p_idx) const;
 	Control *get_tab_control(int p_idx) const;
 	Control *get_current_tab_control() const;
 	Control *get_current_tab_control() const;
 
 

+ 3 - 0
scene/theme/default_theme.cpp

@@ -845,11 +845,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	style_tab_disabled->set_bg_color(style_disabled_color);
 	style_tab_disabled->set_bg_color(style_disabled_color);
 	Ref<StyleBoxFlat> style_tab_hovered = style_tab_unselected->duplicate();
 	Ref<StyleBoxFlat> style_tab_hovered = style_tab_unselected->duplicate();
 	style_tab_hovered->set_bg_color(Color(0.1, 0.1, 0.1, 0.3));
 	style_tab_hovered->set_bg_color(Color(0.1, 0.1, 0.1, 0.3));
+	Ref<StyleBoxFlat> style_tab_focus = focus->duplicate();
 
 
 	theme->set_stylebox("tab_selected", "TabContainer", style_tab_selected);
 	theme->set_stylebox("tab_selected", "TabContainer", style_tab_selected);
 	theme->set_stylebox("tab_hovered", "TabContainer", style_tab_hovered);
 	theme->set_stylebox("tab_hovered", "TabContainer", style_tab_hovered);
 	theme->set_stylebox("tab_unselected", "TabContainer", style_tab_unselected);
 	theme->set_stylebox("tab_unselected", "TabContainer", style_tab_unselected);
 	theme->set_stylebox("tab_disabled", "TabContainer", style_tab_disabled);
 	theme->set_stylebox("tab_disabled", "TabContainer", style_tab_disabled);
+	theme->set_stylebox("tab_focus", "TabContainer", style_tab_focus);
 	theme->set_stylebox("panel", "TabContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0));
 	theme->set_stylebox("panel", "TabContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0));
 	theme->set_stylebox("tabbar_background", "TabContainer", make_empty_stylebox(0, 0, 0, 0));
 	theme->set_stylebox("tabbar_background", "TabContainer", make_empty_stylebox(0, 0, 0, 0));
 
 
@@ -882,6 +884,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_stylebox("tab_hovered", "TabBar", style_tab_hovered);
 	theme->set_stylebox("tab_hovered", "TabBar", style_tab_hovered);
 	theme->set_stylebox("tab_unselected", "TabBar", style_tab_unselected);
 	theme->set_stylebox("tab_unselected", "TabBar", style_tab_unselected);
 	theme->set_stylebox("tab_disabled", "TabBar", style_tab_disabled);
 	theme->set_stylebox("tab_disabled", "TabBar", style_tab_disabled);
+	theme->set_stylebox("tab_focus", "TabBar", style_tab_focus);
 	theme->set_stylebox("button_pressed", "TabBar", button_pressed);
 	theme->set_stylebox("button_pressed", "TabBar", button_pressed);
 	theme->set_stylebox("button_highlight", "TabBar", button_normal);
 	theme->set_stylebox("button_highlight", "TabBar", button_normal);