diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index b3a926456908..1f3c3531c622 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -193,6 +193,9 @@ The focus access mode for the internal [TabBar] node. + + Sets the position of the tab bar. See [enum TabPosition] for details. + [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. @@ -247,6 +250,17 @@ + + + Places the tab bar at the top. + + + Places the tab bar at the bottom. The tab bar's [StyleBox] will be flipped vertically. + + + Represents the size of the [enum TabPosition] enum. + + Modulation color for the [theme_item drop_mark] icon. diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 718ccccc2c27..90a6626b744c 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -516,7 +516,13 @@ void TabBar::_draw_tab(Ref &p_tab_style, Color &p_font_color, int p_in bool rtl = is_layout_rtl(); Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); + if (tab_style_v_flip) { + draw_set_transform(Point2(0.0, p_tab_style->get_draw_rect(sb_rect).size.y), 0.0, Size2(1.0, -1.0)); + } p_tab_style->draw(ci, sb_rect); + if (tab_style_v_flip) { + draw_set_transform(Point2(), 0.0, Size2(1.0, 1.0)); + } if (p_focus) { Ref focus_style = theme_cache.tab_focus_style; focus_style->draw(ci, sb_rect); @@ -1367,6 +1373,10 @@ bool TabBar::get_clip_tabs() const { return clip_tabs; } +void TabBar::set_tab_style_v_flip(bool p_tab_style_v_flip) { + tab_style_v_flip = p_tab_style_v_flip; +} + void TabBar::move_tab(int p_from, int p_to) { if (p_from == p_to) { return; diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 28e3411f3d50..9674187fb3e5 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -91,6 +91,7 @@ class TabBar : public Control { bool clip_tabs = true; int rb_hover = -1; bool rb_pressing = false; + bool tab_style_v_flip = false; bool select_with_rmb = false; @@ -210,6 +211,8 @@ class TabBar : public Control { void set_clip_tabs(bool p_clip_tabs); bool get_clip_tabs() const; + void set_tab_style_v_flip(bool p_tab_style_v_flip); + void move_tab(int p_from, int p_to); void set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 0f461f486585..ef01d9ec5da2 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -35,7 +35,7 @@ #include "scene/gui/texture_rect.h" #include "scene/theme/theme_db.h" -int TabContainer::_get_top_margin() const { +int TabContainer::_get_tab_height() const { int height = 0; if (tabs_visible && get_tab_count() > 0) { height = tab_bar->get_minimum_size().height; @@ -54,31 +54,33 @@ void TabContainer::gui_input(const Ref &p_event) { if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Point2 pos = mb->get_position(); Size2 size = get_size(); + real_t content_height = size.height - _get_tab_height(); // Click must be on tabs in the tab header area. - if (pos.y > _get_top_margin()) { + if (tabs_position == POSITION_TOP && pos.y > _get_tab_height()) { + return; + } + if (tabs_position == POSITION_BOTTOM && pos.y < content_height) { return; } // Handle menu button. - if (is_layout_rtl()) { - if (popup && pos.x < theme_cache.menu_icon->get_width()) { - emit_signal(SNAME("pre_popup_pressed")); - - Vector2 popup_pos = get_screen_position(); - popup_pos.y += theme_cache.menu_icon->get_height(); - - popup->set_position(popup_pos); - popup->popup(); - return; - } - } else { - if (popup && pos.x > size.width - theme_cache.menu_icon->get_width()) { + if (popup) { + if (is_layout_rtl() ? pos.x < theme_cache.menu_icon->get_width() : pos.x > size.width - theme_cache.menu_icon->get_width()) { emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); - popup_pos.x += size.width - popup->get_size().width; - popup_pos.y += theme_cache.menu_icon->get_height(); + if (!is_layout_rtl()) { + popup_pos.x += size.width - popup->get_size().width; + } + popup_pos.y += _get_tab_height() / 2.0; + if (tabs_position == POSITION_BOTTOM) { + popup_pos.y += content_height; + popup_pos.y -= popup->get_size().height; + popup_pos.y -= theme_cache.menu_icon->get_height() / 2.0; + } else { + popup_pos.y += theme_cache.menu_icon->get_height() / 2.0; + } popup->set_position(popup_pos); popup->popup(); @@ -94,7 +96,14 @@ void TabContainer::gui_input(const Ref &p_event) { Size2 size = get_size(); // Mouse must be on tabs in the tab header area. - if (pos.y > _get_top_margin()) { + if (tabs_position == POSITION_TOP && pos.y > _get_tab_height()) { + if (menu_hovered) { + menu_hovered = false; + queue_redraw(); + } + return; + } + if (tabs_position == POSITION_BOTTOM && pos.y < size.height - _get_tab_height()) { if (menu_hovered) { menu_hovered = false; queue_redraw(); @@ -165,21 +174,22 @@ void TabContainer::_notification(int p_what) { return; } - int header_height = _get_top_margin(); + int header_height = _get_tab_height(); + int header_voffset = int(tabs_position == POSITION_BOTTOM) * (size.height - header_height); // Draw background for the tabbar. - theme_cache.tabbar_style->draw(canvas, Rect2(0, 0, size.width, header_height)); + theme_cache.tabbar_style->draw(canvas, Rect2(0, header_voffset, size.width, header_height)); // Draw the background for the tab's content. - theme_cache.panel_style->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); + theme_cache.panel_style->draw(canvas, Rect2(0, int(tabs_position == POSITION_TOP) * header_height, size.width, size.height - header_height)); // Draw the popup menu. if (get_popup()) { int x = is_layout_rtl() ? 0 : get_size().width - theme_cache.menu_icon->get_width(); if (menu_hovered) { - theme_cache.menu_hl_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_hl_icon->get_height()) / 2)); + theme_cache.menu_hl_icon->draw(get_canvas_item(), Point2(x, header_voffset + (header_height - theme_cache.menu_hl_icon->get_height()) / 2)); } else { - theme_cache.menu_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_icon->get_height()) / 2)); + theme_cache.menu_icon->draw(get_canvas_item(), Point2(x, header_voffset + (header_height - theme_cache.menu_icon->get_height()) / 2)); } } } break; @@ -243,6 +253,12 @@ void TabContainer::_repaint() { Vector controls = _get_tab_controls(); int current = get_current_tab(); + if (tabs_position == POSITION_BOTTOM) { + tab_bar->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + } else { + tab_bar->set_anchors_and_offsets_preset(PRESET_TOP_WIDE); + } + for (int i = 0; i < controls.size(); i++) { Control *c = controls[i]; @@ -251,7 +267,11 @@ void TabContainer::_repaint() { c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); if (tabs_visible) { - c->set_offset(SIDE_TOP, _get_top_margin()); + if (tabs_position == POSITION_BOTTOM) { + c->set_offset(SIDE_BOTTOM, -_get_tab_height()); + } else { + c->set_offset(SIDE_TOP, _get_tab_height()); + } } c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_TOP)); @@ -263,6 +283,7 @@ void TabContainer::_repaint() { } } + _update_margins(); update_minimum_size(); } @@ -609,6 +630,23 @@ TabBar::AlignmentMode TabContainer::get_tab_alignment() const { return tab_bar->get_tab_alignment(); } +void TabContainer::set_tabs_position(TabPosition p_tabs_position) { + ERR_FAIL_INDEX(p_tabs_position, POSITION_MAX); + if (p_tabs_position == tabs_position) { + return; + } + tabs_position = p_tabs_position; + + tab_bar->set_tab_style_v_flip(tabs_position == POSITION_BOTTOM); + + callable_mp(this, &TabContainer::_repaint).call_deferred(); + queue_redraw(); +} + +TabContainer::TabPosition TabContainer::get_tabs_position() const { + return tabs_position; +} + void TabContainer::set_tab_focus_mode(Control::FocusMode p_focus_mode) { tab_bar->set_focus_mode(p_focus_mode); } @@ -633,18 +671,8 @@ void TabContainer::set_tabs_visible(bool p_visible) { tabs_visible = p_visible; tab_bar->set_visible(tabs_visible); - Vector controls = _get_tab_controls(); - for (int i = 0; i < controls.size(); i++) { - Control *c = controls[i]; - if (tabs_visible) { - c->set_offset(SIDE_TOP, _get_top_margin()); - } else { - c->set_offset(SIDE_TOP, 0); - } - } - + callable_mp(this, &TabContainer::_repaint).call_deferred(); queue_redraw(); - update_minimum_size(); } bool TabContainer::are_tabs_visible() const { @@ -890,6 +918,8 @@ void TabContainer::_bind_methods() { 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("get_tab_alignment"), &TabContainer::get_tab_alignment); + ClassDB::bind_method(D_METHOD("set_tabs_position", "tabs_position"), &TabContainer::set_tabs_position); + ClassDB::bind_method(D_METHOD("get_tabs_position"), &TabContainer::get_tabs_position); ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &TabContainer::set_clip_tabs); ClassDB::bind_method(D_METHOD("get_clip_tabs"), &TabContainer::get_clip_tabs); ClassDB::bind_method(D_METHOD("set_tabs_visible", "visible"), &TabContainer::set_tabs_visible); @@ -931,6 +961,7 @@ void TabContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_position", PROPERTY_HINT_ENUM, "Top,Bottom"), "set_tabs_position", "get_tabs_position"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "all_tabs_in_front"), "set_all_tabs_in_front", "is_all_tabs_in_front"); @@ -939,6 +970,10 @@ 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"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_tab_focus_mode", "get_tab_focus_mode"); + BIND_ENUM_CONSTANT(POSITION_TOP); + BIND_ENUM_CONSTANT(POSITION_BOTTOM); + BIND_ENUM_CONSTANT(POSITION_MAX); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TabContainer, side_margin); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabContainer, panel_style, "panel"); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 450143cd0cf6..0c645b4598a7 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -38,9 +38,18 @@ class TabContainer : public Container { GDCLASS(TabContainer, Container); +public: + enum TabPosition { + POSITION_TOP, + POSITION_BOTTOM, + POSITION_MAX, + }; + +private: TabBar *tab_bar = nullptr; bool tabs_visible = true; bool all_tabs_in_front = false; + TabPosition tabs_position = POSITION_TOP; bool menu_hovered = false; mutable ObjectID popup_obj_id; bool use_hidden_tabs_for_min_size = false; @@ -86,7 +95,7 @@ class TabContainer : public Container { int tab_font_size; } theme_cache; - int _get_top_margin() const; + int _get_tab_height() const; Vector _get_tab_controls() const; void _on_theme_changed(); void _repaint(); @@ -124,6 +133,9 @@ class TabContainer : public Container { void set_tab_alignment(TabBar::AlignmentMode p_alignment); TabBar::AlignmentMode get_tab_alignment() const; + void set_tabs_position(TabPosition p_tab_position); + TabPosition get_tabs_position() const; + void set_tab_focus_mode(FocusMode p_focus_mode); FocusMode get_tab_focus_mode() const; @@ -185,4 +197,6 @@ class TabContainer : public Container { TabContainer(); }; +VARIANT_ENUM_CAST(TabContainer::TabPosition); + #endif // TAB_CONTAINER_H