瀏覽代碼

Merge pull request #82468 from kitbdev/tabcontainer-bottom

Option to put TabContainer tabs at bottom
Rémi Verschelde 1 年之前
父節點
當前提交
129332e3b9
共有 5 個文件被更改,包括 112 次插入36 次删除
  1. 14 0
      doc/classes/TabContainer.xml
  2. 10 0
      scene/gui/tab_bar.cpp
  3. 3 0
      scene/gui/tab_bar.h
  4. 70 35
      scene/gui/tab_container.cpp
  5. 15 1
      scene/gui/tab_container.h

+ 14 - 0
doc/classes/TabContainer.xml

@@ -193,6 +193,9 @@
 		<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_position" type="int" setter="set_tabs_position" getter="get_tabs_position" enum="TabContainer.TabPosition" default="0">
+			Sets the position of the tab bar. See [enum TabPosition] for details.
+		</member>
 		<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].
 			Setting this to [code]-1[/code] will disable rearranging between [TabContainer]s.
@@ -247,6 +250,17 @@
 			</description>
 		</signal>
 	</signals>
+	<constants>
+		<constant name="POSITION_TOP" value="0" enum="TabPosition">
+			Places the tab bar at the top.
+		</constant>
+		<constant name="POSITION_BOTTOM" value="1" enum="TabPosition">
+			Places the tab bar at the bottom. The tab bar's [StyleBox] will be flipped vertically.
+		</constant>
+		<constant name="POSITION_MAX" value="2" enum="TabPosition">
+			Represents the size of the [enum TabPosition] enum.
+		</constant>
+	</constants>
 	<theme_items>
 		<theme_item name="drop_mark_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
 			Modulation color for the [theme_item drop_mark] icon.

+ 10 - 0
scene/gui/tab_bar.cpp

@@ -516,7 +516,13 @@ void TabBar::_draw_tab(Ref<StyleBox> &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<StyleBox> 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;

+ 3 - 0
scene/gui/tab_bar.h

@@ -91,6 +91,7 @@ private:
 	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 @@ public:
 	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);

+ 70 - 35
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<InputEvent> &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<InputEvent> &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<Control *> 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<Control *> 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");

+ 15 - 1
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<Control *> _get_tab_controls() const;
 	void _on_theme_changed();
 	void _repaint();
@@ -124,6 +133,9 @@ public:
 	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 @@ public:
 	TabContainer();
 };
 
+VARIANT_ENUM_CAST(TabContainer::TabPosition);
+
 #endif // TAB_CONTAINER_H