summaryrefslogtreecommitdiffstats
path: root/scene/gui
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2023-09-25 17:17:51 +0200
committerRémi Verschelde <rverschelde@gmail.com>2023-09-25 17:17:51 +0200
commitc57d9f348343f7c8f670a25db4acf7f6be338bf7 (patch)
tree2e446646dedf2fd822a235851a9844a8971f51d5 /scene/gui
parent1a0e653d7f1987bc3593bd50d7ae37c954883d3f (diff)
parent18811ac8141f2b8dfb45413400643af11d09f2b0 (diff)
downloadredot-engine-c57d9f348343f7c8f670a25db4acf7f6be338bf7.tar.gz
Merge pull request #79104 from DrRevert/focusable_tabs_refactor
Allow to focus individual tabs in `TabBar`/`TabContainer`
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/tab_bar.cpp91
-rw-r--r--scene/gui/tab_bar.h10
-rw-r--r--scene/gui/tab_container.cpp23
-rw-r--r--scene/gui/tab_container.h7
4 files changed, 127 insertions, 4 deletions
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index a9c16a9942..14eed4c7a2 100644
--- a/scene/gui/tab_bar.cpp
+++ b/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) {
@@ -307,6 +335,28 @@ void TabBar::_shape(int p_tab) {
void TabBar::_notification(int 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: {
queue_redraw();
} break;
@@ -379,7 +429,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);
+ _draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs, false);
}
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;
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) {
@@ -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();
bool rtl = is_layout_rtl();
Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height);
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);
@@ -607,6 +661,33 @@ int TabBar::get_hovered_tab() const {
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 {
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("get_current_tab"), &TabBar::get_current_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("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);
@@ -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_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_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_hl_icon, "increment_highlight");
@@ -1699,5 +1783,6 @@ void TabBar::_bind_methods() {
TabBar::TabBar() {
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));
}
diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h
index d89b1866bf..b79c170a7b 100644
--- a/scene/gui/tab_bar.h
+++ b/scene/gui/tab_bar.h
@@ -107,6 +107,10 @@ private:
bool scroll_to_selected = true;
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 {
int h_separation = 0;
int icon_max_width = 0;
@@ -115,6 +119,7 @@ private:
Ref<StyleBox> tab_hovered_style;
Ref<StyleBox> tab_selected_style;
Ref<StyleBox> tab_disabled_style;
+ Ref<StyleBox> tab_focus_style;
Ref<Texture2D> increment_icon;
Ref<Texture2D> increment_hl_icon;
@@ -148,7 +153,7 @@ private:
void _on_mouse_exited();
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:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
@@ -214,6 +219,9 @@ public:
int get_previous_tab() const;
int get_hovered_tab() const;
+ bool select_previous_available();
+ bool select_next_available();
+
int get_tab_offset() const;
bool get_offset_buttons_visible() const;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index bad85560d1..b757b516d1 100644
--- a/scene/gui/tab_container.cpp
+++ b/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_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_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_highlight"), theme_cache.increment_hl_icon);
@@ -605,6 +606,14 @@ int TabContainer::get_previous_tab() const {
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 {
Vector<Control *> controls = _get_tab_controls();
if (p_idx >= 0 && p_idx < controls.size()) {
@@ -649,6 +658,14 @@ TabBar::AlignmentMode TabContainer::get_tab_alignment() const {
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) {
tab_bar->set_clip_tabs(p_clip_tabs);
}
@@ -915,6 +932,8 @@ void TabContainer::_bind_methods() {
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_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_tab_bar"), &TabContainer::get_tab_bar);
ClassDB::bind_method(D_METHOD("get_tab_control", "tab_idx"), &TabContainer::get_tab_control);
@@ -948,6 +967,8 @@ void TabContainer::_bind_methods() {
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("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("tab_changed", PropertyInfo(Variant::INT, "tab")));
@@ -965,6 +986,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::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::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);
@@ -982,6 +1004,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_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_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_hl_icon, "increment_highlight");
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index 9919565f1e..a831416612 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -66,6 +66,7 @@ class TabContainer : public Container {
Ref<StyleBox> tab_hovered_style;
Ref<StyleBox> tab_selected_style;
Ref<StyleBox> tab_disabled_style;
+ Ref<StyleBox> tab_focus_style;
Ref<Texture2D> increment_icon;
Ref<Texture2D> increment_hl_icon;
@@ -119,6 +120,9 @@ public:
void set_tab_alignment(TabBar::AlignmentMode p_alignment);
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);
bool get_clip_tabs() const;
@@ -151,6 +155,9 @@ public:
int get_current_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_current_tab_control() const;