Browse Source

Refactor Bottom Panel to be a TabContainer

Logan Detrick 5 months ago
parent
commit
e2caff9a06

+ 1 - 1
doc/classes/EditorPlugin.xml

@@ -430,7 +430,7 @@
 			<param index="1" name="title" type="String" />
 			<param index="1" name="title" type="String" />
 			<param index="2" name="shortcut" type="Shortcut" default="null" />
 			<param index="2" name="shortcut" type="Shortcut" default="null" />
 			<description>
 			<description>
-				Adds a control to the bottom panel (together with Output, Debug, Animation, etc.). Returns a reference to the button added. It's up to you to hide/show the button when needed. When your plugin is deactivated, make sure to remove your custom control with [method remove_control_from_bottom_panel] and free it with [method Node.queue_free].
+				Adds a control to the bottom panel (together with Output, Debug, Animation, etc.). Returns a reference to a button that is outside the scene tree. It's up to you to hide/show the button when needed. When your plugin is deactivated, make sure to remove your custom control with [method remove_control_from_bottom_panel] and free it with [method Node.queue_free].
 				Optionally, you can specify a shortcut parameter. When pressed, this shortcut will toggle the bottom panel's visibility. See the default editor bottom panel shortcuts in the Editor Settings for inspiration. Per convention, they all use [kbd]Alt[/kbd] modifier.
 				Optionally, you can specify a shortcut parameter. When pressed, this shortcut will toggle the bottom panel's visibility. See the default editor bottom panel shortcuts in the Editor Settings for inspiration. Per convention, they all use [kbd]Alt[/kbd] modifier.
 			</description>
 			</description>
 		</method>
 		</method>

+ 1 - 2
editor/debugger/debugger_editor_plugin.cpp

@@ -55,8 +55,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) {
 	file_server = memnew(EditorFileServer);
 	file_server = memnew(EditorFileServer);
 
 
 	EditorDebuggerNode *debugger = memnew(EditorDebuggerNode);
 	EditorDebuggerNode *debugger = memnew(EditorDebuggerNode);
-	Button *db = EditorNode::get_bottom_panel()->add_item(TTRC("Debugger"), debugger, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_debugger_bottom_panel", TTRC("Toggle Debugger Bottom Panel"), KeyModifierMask::ALT | Key::D));
-	debugger->set_tool_button(db);
+	EditorNode::get_bottom_panel()->add_item(TTRC("Debugger"), debugger, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_debugger_bottom_panel", TTRC("Toggle Debugger Bottom Panel"), KeyModifierMask::ALT | Key::D));
 
 
 	// Main editor debug menu.
 	// Main editor debug menu.
 	debug_menu = p_debug_menu;
 	debug_menu = p_debug_menu;

+ 21 - 12
editor/debugger/editor_debugger_node.cpp

@@ -441,26 +441,35 @@ void EditorDebuggerNode::_update_errors() {
 			dbg->update_tabs();
 			dbg->update_tabs();
 		});
 		});
 
 
+		last_error_count = error_count;
+		last_warning_count = warning_count;
+
+		// TODO: Replace logic when EditorDock class is merged to be more flexible.
+		TabContainer *parent = Object::cast_to<TabContainer>(get_parent());
+		if (!parent) {
+			return;
+		}
+
+		int idx = parent->get_tab_idx_from_control(this);
+
 		if (error_count == 0 && warning_count == 0) {
 		if (error_count == 0 && warning_count == 0) {
-			debugger_button->set_text(TTR("Debugger"));
-			debugger_button->remove_theme_color_override(SceneStringName(font_color));
-			debugger_button->set_button_icon(Ref<Texture2D>());
+			set_name(TTR("Debugger"));
+			parent->set_tab_icon(idx, Ref<Texture2D>());
+			parent->get_tab_bar()->set_font_color_override_all(idx, Color(0, 0, 0, 0));
 		} else {
 		} else {
-			debugger_button->set_text(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")");
+			set_name(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")");
 			if (error_count >= 1 && warning_count >= 1) {
 			if (error_count >= 1 && warning_count >= 1) {
-				debugger_button->set_button_icon(get_editor_theme_icon(SNAME("ErrorWarning")));
+				parent->set_tab_icon(idx, get_editor_theme_icon(SNAME("ErrorWarning")));
 				// Use error color to represent the highest level of severity reported.
 				// Use error color to represent the highest level of severity reported.
-				debugger_button->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+				parent->get_tab_bar()->set_font_color_override_all(idx, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
 			} else if (error_count >= 1) {
 			} else if (error_count >= 1) {
-				debugger_button->set_button_icon(get_editor_theme_icon(SNAME("Error")));
-				debugger_button->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+				parent->set_tab_icon(idx, get_editor_theme_icon(SNAME("Error")));
+				parent->get_tab_bar()->set_font_color_override_all(idx, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
 			} else {
 			} else {
-				debugger_button->set_button_icon(get_editor_theme_icon(SNAME("Warning")));
-				debugger_button->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
+				parent->set_tab_icon(idx, get_editor_theme_icon(SNAME("Warning")));
+				parent->get_tab_bar()->set_font_color_override_all(idx, get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
 			}
 			}
 		}
 		}
-		last_error_count = error_count;
-		last_warning_count = warning_count;
 	}
 	}
 }
 }
 
 

+ 0 - 5
editor/debugger/editor_debugger_node.h

@@ -93,7 +93,6 @@ private:
 
 
 	Ref<EditorDebuggerServer> server;
 	Ref<EditorDebuggerServer> server;
 	TabContainer *tabs = nullptr;
 	TabContainer *tabs = nullptr;
-	Button *debugger_button = nullptr;
 	MenuButton *script_menu = nullptr;
 	MenuButton *script_menu = nullptr;
 
 
 	Ref<Script> stack_script; // Why?!?
 	Ref<Script> stack_script; // Why?!?
@@ -180,10 +179,6 @@ public:
 
 
 	void set_script_debug_button(MenuButton *p_button);
 	void set_script_debug_button(MenuButton *p_button);
 
 
-	void set_tool_button(Button *p_button) {
-		debugger_button = p_button;
-	}
-
 	String get_var_value(const String &p_var) const;
 	String get_var_value(const String &p_var) const;
 	Ref<Script> get_dump_stack_script() const { return stack_script; } // Why do we need this?
 	Ref<Script> get_dump_stack_script() const { return stack_script; } // Why do we need this?
 
 

+ 7 - 14
editor/docks/editor_dock_manager.cpp

@@ -298,20 +298,14 @@ void EditorDockManager::_dock_container_gui_input(const Ref<InputEvent> &p_input
 			return;
 			return;
 		}
 		}
 
 
-		// Right click context menu.
-		dock_context_popup->set_dock(Object::cast_to<EditorDock>(p_dock_container->get_tab_control(tab_id)));
-		dock_context_popup->set_position(p_dock_container->get_screen_position() + mb->get_position());
-		dock_context_popup->popup();
-	}
-}
-
-void EditorDockManager::_bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, EditorDock *p_dock, Button *p_bottom_button) {
-	Ref<InputEventMouseButton> mb = p_input;
+		EditorDock *hovered_dock = Object::cast_to<EditorDock>(p_dock_container->get_tab_control(tab_id));
+		if (hovered_dock == nullptr) {
+			return;
+		}
 
 
-	if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
 		// Right click context menu.
 		// Right click context menu.
-		dock_context_popup->set_dock(p_dock);
-		dock_context_popup->set_position(p_bottom_button->get_screen_position() + mb->get_position());
+		dock_context_popup->set_dock(hovered_dock);
+		dock_context_popup->set_position(p_dock_container->get_tab_bar()->get_screen_position() + mb->get_position());
 		dock_context_popup->popup();
 		dock_context_popup->popup();
 	}
 	}
 }
 }
@@ -467,8 +461,7 @@ void EditorDockManager::_dock_move_to_bottom(EditorDock *p_dock, bool p_visible)
 	p_dock->update_layout(EditorDock::DOCK_LAYOUT_HORIZONTAL);
 	p_dock->update_layout(EditorDock::DOCK_LAYOUT_HORIZONTAL);
 
 
 	// Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
 	// Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
-	Button *bottom_button = EditorNode::get_bottom_panel()->add_item(p_dock->get_display_title(), p_dock, p_dock->shortcut, true);
-	bottom_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_bottom_dock_button_gui_input).bind(bottom_button).bind(p_dock));
+	EditorNode::get_bottom_panel()->add_item(p_dock->get_display_title(), p_dock, p_dock->shortcut, true);
 	EditorNode::get_bottom_panel()->make_item_visible(p_dock, p_visible);
 	EditorNode::get_bottom_panel()->make_item_visible(p_dock, p_visible);
 }
 }
 
 

+ 1 - 1
editor/docks/editor_dock_manager.h

@@ -83,6 +83,7 @@ public:
 private:
 private:
 	friend class DockContextPopup;
 	friend class DockContextPopup;
 	friend class EditorDockDragHint;
 	friend class EditorDockDragHint;
+	friend class EditorBottomPanel; // TODO: Temporary until DOCK_SLOT_BOTTOM registered. Used to connect signals.
 
 
 	static inline EditorDockManager *singleton = nullptr;
 	static inline EditorDockManager *singleton = nullptr;
 
 
@@ -106,7 +107,6 @@ private:
 	void _dock_drag_stopped();
 	void _dock_drag_stopped();
 	void _dock_split_dragged(int p_offset);
 	void _dock_split_dragged(int p_offset);
 	void _dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container);
 	void _dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container);
-	void _bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, EditorDock *p_dock, Button *p_bottom_button);
 	void _dock_container_update_visibility(TabContainer *p_dock_container);
 	void _dock_container_update_visibility(TabContainer *p_dock_container);
 	void _update_layout();
 	void _update_layout();
 
 

+ 11 - 5
editor/editor_log.cpp

@@ -42,6 +42,7 @@
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_scale.h"
 #include "modules/regex/regex.h"
 #include "modules/regex/regex.h"
 #include "scene/gui/separator.h"
 #include "scene/gui/separator.h"
+#include "scene/gui/tab_container.h"
 #include "scene/main/timer.h"
 #include "scene/main/timer.h"
 #include "scene/resources/font.h"
 #include "scene/resources/font.h"
 
 
@@ -223,7 +224,7 @@ void EditorLog::_clear_request() {
 	log->clear();
 	log->clear();
 	messages.clear();
 	messages.clear();
 	_reset_message_counts();
 	_reset_message_counts();
-	tool_button->set_button_icon(Ref<Texture2D>());
+	_set_dock_tab_icon(Ref<Texture2D>());
 }
 }
 
 
 void EditorLog::_copy_request() {
 void EditorLog::_copy_request() {
@@ -274,8 +275,13 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) {
 	}
 	}
 }
 }
 
 
-void EditorLog::set_tool_button(Button *p_tool_button) {
-	tool_button = p_tool_button;
+void EditorLog::_set_dock_tab_icon(Ref<Texture2D> p_icon) {
+	// This is the sole reason to include "tab_container.h" here.
+	TabContainer *parent = Object::cast_to<TabContainer>(get_parent());
+	if (parent) {
+		int idx = parent->get_tab_idx_from_control(this);
+		parent->set_tab_icon(idx, p_icon);
+	}
 }
 }
 
 
 void EditorLog::register_undo_redo(UndoRedo *p_undo_redo) {
 void EditorLog::register_undo_redo(UndoRedo *p_undo_redo) {
@@ -401,7 +407,7 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) {
 			log->push_bold();
 			log->push_bold();
 			log->add_text(" ERROR: ");
 			log->add_text(" ERROR: ");
 			log->pop(); // bold
 			log->pop(); // bold
-			tool_button->set_button_icon(icon);
+			_set_dock_tab_icon(icon);
 		} break;
 		} break;
 		case MSG_TYPE_WARNING: {
 		case MSG_TYPE_WARNING: {
 			log->push_color(theme_cache.warning_color);
 			log->push_color(theme_cache.warning_color);
@@ -410,7 +416,7 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) {
 			log->push_bold();
 			log->push_bold();
 			log->add_text(" WARNING: ");
 			log->add_text(" WARNING: ");
 			log->pop(); // bold
 			log->pop(); // bold
-			tool_button->set_button_icon(icon);
+			_set_dock_tab_icon(icon);
 		} break;
 		} break;
 		case MSG_TYPE_EDITOR: {
 		case MSG_TYPE_EDITOR: {
 			// Distinguish editor messages from messages printed by the project
 			// Distinguish editor messages from messages printed by the project

+ 1 - 4
editor/editor_log.h

@@ -143,9 +143,6 @@ private:
 	// Reusable RichTextLabel for BBCode parsing during search
 	// Reusable RichTextLabel for BBCode parsing during search
 	RichTextLabel *bbcode_parser = nullptr;
 	RichTextLabel *bbcode_parser = nullptr;
 
 
-	// Reference to the "Output" button on the toolbar so we can update its icon when warnings or errors are encountered.
-	Button *tool_button = nullptr;
-
 	bool is_loading_state = false; // Used to disable saving requests while loading (some signals from buttons will try to trigger a save, which happens during loading).
 	bool is_loading_state = false; // Used to disable saving requests while loading (some signals from buttons will try to trigger a save, which happens during loading).
 	Timer *save_state_timer = nullptr;
 	Timer *save_state_timer = nullptr;
 
 
@@ -169,6 +166,7 @@ private:
 
 
 	void _process_message(const String &p_msg, MessageType p_type, bool p_clear);
 	void _process_message(const String &p_msg, MessageType p_type, bool p_clear);
 	void _reset_message_counts();
 	void _reset_message_counts();
+	void _set_dock_tab_icon(Ref<Texture2D> p_icon);
 
 
 	void _set_collapse(bool p_collapse);
 	void _set_collapse(bool p_collapse);
 
 
@@ -184,7 +182,6 @@ protected:
 
 
 public:
 public:
 	void add_message(const String &p_msg, MessageType p_type = MSG_TYPE_STD);
 	void add_message(const String &p_msg, MessageType p_type = MSG_TYPE_STD);
-	void set_tool_button(Button *p_tool_button);
 	void register_undo_redo(UndoRedo *p_undo_redo);
 	void register_undo_redo(UndoRedo *p_undo_redo);
 	void deinit();
 	void deinit();
 
 

+ 3 - 3
editor/editor_node.cpp

@@ -677,7 +677,7 @@ void EditorNode::_update_theme(bool p_skip_creation) {
 		}
 		}
 
 
 		editor_main_screen->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
 		editor_main_screen->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
-		bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
+		bottom_panel->_theme_changed();
 		distraction_free->set_button_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
 		distraction_free->set_button_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
 		distraction_free->add_theme_style_override(SceneStringName(pressed), theme->get_stylebox(CoreStringName(normal), "FlatMenuButton"));
 		distraction_free->add_theme_style_override(SceneStringName(pressed), theme->get_stylebox(CoreStringName(normal), "FlatMenuButton"));
 
 
@@ -8525,12 +8525,12 @@ EditorNode::EditorNode() {
 	// Bottom panels.
 	// Bottom panels.
 
 
 	bottom_panel = memnew(EditorBottomPanel);
 	bottom_panel = memnew(EditorBottomPanel);
+	bottom_panel->set_theme_type_variation("BottomPanel");
 	center_split->add_child(bottom_panel);
 	center_split->add_child(bottom_panel);
 	center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
 	center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
 
 
 	log = memnew(EditorLog);
 	log = memnew(EditorLog);
-	Button *output_button = bottom_panel->add_item(TTRC("Output"), log, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_output_bottom_panel", TTRC("Toggle Output Bottom Panel"), KeyModifierMask::ALT | Key::O));
-	log->set_tool_button(output_button);
+	bottom_panel->add_item(TTRC("Output"), log, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_output_bottom_panel", TTRC("Toggle Output Bottom Panel"), KeyModifierMask::ALT | Key::O));
 
 
 	center_split->connect(SceneStringName(resized), callable_mp(this, &EditorNode::_vp_resized));
 	center_split->connect(SceneStringName(resized), callable_mp(this, &EditorNode::_vp_resized));
 
 

+ 112 - 234
editor/gui/editor_bottom_panel.cpp

@@ -31,6 +31,7 @@
 #include "editor_bottom_panel.h"
 #include "editor_bottom_panel.h"
 
 
 #include "editor/debugger/editor_debugger_node.h"
 #include "editor/debugger/editor_debugger_node.h"
+#include "editor/docks/editor_dock_manager.h"
 #include "editor/editor_node.h"
 #include "editor/editor_node.h"
 #include "editor/editor_string_names.h"
 #include "editor/editor_string_names.h"
 #include "editor/gui/editor_toaster.h"
 #include "editor/gui/editor_toaster.h"
@@ -39,7 +40,6 @@
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_scale.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/button.h"
 #include "scene/gui/button.h"
-#include "scene/gui/scroll_container.h"
 #include "scene/gui/split_container.h"
 #include "scene/gui/split_container.h"
 
 
 void EditorBottomPanel::_notification(int p_what) {
 void EditorBottomPanel::_notification(int p_what) {
@@ -47,297 +47,167 @@ void EditorBottomPanel::_notification(int p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
 			pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
 			expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
 			expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
-			left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
-			right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
-		} break;
-
-		case NOTIFICATION_TRANSLATION_CHANGED:
-		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
-			if (is_layout_rtl()) {
-				bottom_hbox->move_child(left_button, button_scroll->get_index() + 1);
-				bottom_hbox->move_child(right_button, 0);
-			} else {
-				bottom_hbox->move_child(right_button, button_scroll->get_index() + 1);
-				bottom_hbox->move_child(left_button, 0);
-			}
 		} break;
 		} break;
 	}
 	}
 }
 }
 
 
-void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock) {
-	for (int i = 0; i < items.size(); i++) {
-		if (items[i].control == p_control) {
-			_switch_to_item(p_visible, i, p_ignore_lock);
-			return;
-		}
-	}
+void EditorBottomPanel::_on_tab_changed(int p_idx) {
+	callable_mp(this, &EditorBottomPanel::_repaint).call_deferred();
 }
 }
 
 
-void EditorBottomPanel::_scroll(bool p_right) {
-	HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
-	if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
-		h_scroll->set_value(p_right ? h_scroll->get_max() : 0);
-	} else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
-		h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * (p_right ? 1 : -1));
-	} else {
-		h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * 0.5) * (p_right ? 1 : -1));
-	}
-}
-
-void EditorBottomPanel::_update_scroll_buttons() {
-	bool show_arrows = button_hbox->get_size().width > button_scroll->get_size().width;
-	left_button->set_visible(show_arrows);
-	right_button->set_visible(show_arrows);
+void EditorBottomPanel::_theme_changed() {
+	Ref<StyleBox> bottom_tabbar_style = get_theme_stylebox("tabbar_background", "BottomPanel")->duplicate();
+	bottom_tabbar_style->set_content_margin(SIDE_RIGHT, bottom_hbox->get_minimum_size().x + bottom_tabbar_style->get_content_margin(SIDE_LEFT));
+	add_theme_style_override("tabbar_background", bottom_tabbar_style);
 
 
-	if (show_arrows) {
-		_update_disabled_buttons();
+	if (get_current_tab() == -1) {
+		// Hide panel when not showing anything.
+		remove_theme_style_override(SceneStringName(panel));
+	} else {
+		add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
 	}
 	}
 }
 }
 
 
-void EditorBottomPanel::_update_disabled_buttons() {
-	HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
-	left_button->set_disabled(h_scroll->get_value() == 0);
-	right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max());
-}
-
-void EditorBottomPanel::_ensure_control_visible(ObjectID p_id) {
-	Control *c = ObjectDB::get_instance<Control>(p_id);
-	if (!c) {
+void EditorBottomPanel::_repaint() {
+	bool panel_collapsed = get_current_tab() == -1;
+	if (panel_collapsed == (get_previous_tab() == -1)) {
 		return;
 		return;
 	}
 	}
 
 
-	button_scroll->ensure_control_visible(c);
-}
+	SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
+	ERR_FAIL_NULL(center_split);
 
 
-void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) {
-	ERR_FAIL_INDEX(p_idx, items.size());
+	center_split->set_dragger_visibility(panel_collapsed ? SplitContainer::DRAGGER_HIDDEN : SplitContainer::DRAGGER_VISIBLE);
+	center_split->set_collapsed(panel_collapsed);
 
 
-	if (items[p_idx].control->is_visible() == p_visible) {
-		return;
+	pin_button->set_visible(!panel_collapsed);
+	expand_button->set_visible(!panel_collapsed);
+	if (expand_button->is_pressed()) {
+		EditorNode::get_top_split()->set_visible(panel_collapsed);
 	}
 	}
 
 
-	SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
-	ERR_FAIL_NULL(center_split);
+	_theme_changed();
+}
 
 
-	if (p_visible) {
-		if (!p_ignore_lock && lock_panel_switching && pin_button->is_visible()) {
-			return;
-		}
+void EditorBottomPanel::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
+	p_config_file->set_value(p_section, "selected_bottom_panel_item", get_current_tab() != -1 ? Variant(get_current_tab()) : Variant());
+}
+
+void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
+	if (p_config_file->has_section_key(p_section, "selected_bottom_panel_item")) {
+		int stored_current_tab = p_config_file->get_value(p_section, "selected_bottom_panel_item");
 
 
-		for (int i = 0; i < items.size(); i++) {
-			items[i].button->set_pressed_no_signal(i == p_idx);
-			items[i].control->set_visible(i == p_idx);
+		if (stored_current_tab >= 0 && stored_current_tab < get_tab_bar()->get_tab_count()) {
+			// Make sure we don't try to open contextual editors which are not enabled in the current context.
+			if (!get_tab_bar()->is_tab_hidden(stored_current_tab)) {
+				set_current_tab(stored_current_tab);
+				return;
+			}
 		}
 		}
+	}
 
 
-		center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
-		center_split->set_collapsed(false);
-		pin_button->show();
+	// If there is no active tab we need to collapse the panel.
+	set_current_tab(-1);
+}
 
 
-		expand_button->show();
-		if (expand_button->is_pressed()) {
-			EditorNode::get_top_split()->hide();
-		}
-		callable_mp(this, &EditorBottomPanel::_ensure_control_visible).call_deferred(items[p_idx].button->get_instance_id());
-	} else {
-		items[p_idx].button->set_pressed_no_signal(false);
-		items[p_idx].control->set_visible(false);
-		center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
-		center_split->set_collapsed(true);
-		pin_button->hide();
-
-		expand_button->hide();
-		if (expand_button->is_pressed()) {
-			EditorNode::get_top_split()->show();
-		}
+void EditorBottomPanel::make_item_visible(Control *p_item, bool p_visible, bool p_ignore_lock) {
+	// Don't allow changing tabs involuntarily when tabs are locked.
+	if (!p_ignore_lock && lock_panel_switching && pin_button->is_visible()) {
+		return;
 	}
 	}
 
 
-	last_opened_control = items[p_idx].control;
+	p_item->set_visible(p_visible);
 }
 }
 
 
-void EditorBottomPanel::_pin_button_toggled(bool p_pressed) {
-	lock_panel_switching = p_pressed;
+void EditorBottomPanel::move_item_to_end(Control *p_item) {
+	move_child(p_item, -1);
 }
 }
 
 
-void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
-	EditorNode::get_top_split()->set_visible(!p_pressed);
+void EditorBottomPanel::hide_bottom_panel() {
+	set_current_tab(-1);
 }
 }
 
 
-bool EditorBottomPanel::_button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control) {
-	if (!p_button->is_pressed()) {
-		_switch_by_control(true, p_control, true);
-	}
-	return false;
+void EditorBottomPanel::toggle_last_opened_bottom_panel() {
+	set_current_tab(get_current_tab() == -1 ? get_previous_tab() : -1);
 }
 }
 
 
-void EditorBottomPanel::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
-	int selected_item_idx = -1;
-	for (int i = 0; i < items.size(); i++) {
-		if (items[i].button->is_pressed()) {
-			selected_item_idx = i;
+void EditorBottomPanel::shortcut_input(const Ref<InputEvent> &p_event) {
+	if (p_event.is_null() || !p_event->is_pressed() || p_event->is_echo()) {
+		return;
+	}
+
+	for (uint32_t i = 0; i < dock_shortcuts.size(); i++) {
+		if (dock_shortcuts[i].is_valid() && dock_shortcuts[i]->matches_event(p_event)) {
+			bottom_docks[i]->set_visible(!bottom_docks[i]->is_visible());
 			break;
 			break;
 		}
 		}
 	}
 	}
-	if (selected_item_idx != -1) {
-		p_config_file->set_value(p_section, "selected_bottom_panel_item", selected_item_idx);
-	} else {
-		p_config_file->set_value(p_section, "selected_bottom_panel_item", Variant());
-	}
 }
 }
 
 
-void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
-	bool has_active_tab = false;
-	if (p_config_file->has_section_key(p_section, "selected_bottom_panel_item")) {
-		int selected_item_idx = p_config_file->get_value(p_section, "selected_bottom_panel_item");
-		if (selected_item_idx >= 0 && selected_item_idx < items.size()) {
-			// Make sure we don't try to open contextual editors which are not enabled in the current context.
-			if (items[selected_item_idx].button->is_visible()) {
-				_switch_to_item(true, selected_item_idx);
-				has_active_tab = true;
-			}
-		}
-	}
-	// If there is no active tab we need to collapse the panel.
-	if (!has_active_tab) {
-		items[0].control->show(); // _switch_to_item() can collapse only visible tabs.
-		_switch_to_item(false, 0);
-	}
+void EditorBottomPanel::_pin_button_toggled(bool p_pressed) {
+	lock_panel_switching = p_pressed;
 }
 }
 
 
-Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
-	Button *tb = memnew(Button);
-	tb->set_theme_type_variation("BottomPanelButton");
-	tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item, true));
-	tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
-	tb->set_text(p_text);
-	tb->set_shortcut(p_shortcut);
-	tb->set_toggle_mode(true);
-	tb->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
-	item_vbox->add_child(p_item);
+void EditorBottomPanel::set_expanded(bool p_expanded) {
+	expand_button->set_pressed(p_expanded);
+}
 
 
-	bottom_hbox->move_to_front();
-	button_hbox->add_child(tb);
-	if (p_at_front) {
-		button_hbox->move_child(tb, 0);
-	}
-	p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-	p_item->hide();
+void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
+	EditorNode::get_top_split()->set_visible(!p_pressed);
+}
 
 
-	BottomPanelItem bpi;
-	bpi.button = tb;
-	bpi.control = p_item;
-	bpi.name = p_text;
+Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
+	p_item->set_name(p_text);
+	add_child(p_item);
 	if (p_at_front) {
 	if (p_at_front) {
-		items.insert(0, bpi);
-	} else {
-		items.push_back(bpi);
+		move_child(p_item, 0);
 	}
 	}
+	bottom_docks.push_back(p_item);
+	dock_shortcuts.push_back(p_shortcut);
 
 
+	set_process_shortcut_input(is_processing_shortcut_input() || p_shortcut.is_valid());
+
+	// Still return a dummy button for compatibility reasons.
+	Button *tb = memnew(Button);
+	tb->set_toggle_mode(true);
+	tb->connect(SceneStringName(visibility_changed), callable_mp(this, &EditorBottomPanel::_on_button_visibility_changed).bind(tb, p_item));
+	legacy_buttons.push_back(tb);
 	return tb;
 	return tb;
 }
 }
 
 
 void EditorBottomPanel::remove_item(Control *p_item) {
 void EditorBottomPanel::remove_item(Control *p_item) {
-	bool was_visible = false;
-	for (int i = 0; i < items.size(); i++) {
-		if (items[i].control == p_item) {
-			if (p_item->is_visible_in_tree()) {
-				was_visible = true;
-			}
-			item_vbox->remove_child(items[i].control);
-			button_hbox->remove_child(items[i].button);
-			memdelete(items[i].button);
-			items.remove_at(i);
-			break;
-		}
-	}
+	int item_idx = bottom_docks.find(p_item);
+	ERR_FAIL_COND_MSG(item_idx == -1, vformat("Cannot remove unknown dock \"%s\" from the bottom panel.", p_item->get_name()));
 
 
-	if (was_visible) {
-		// Open the first panel to ensure that if the removed dock was visible, the bottom
-		// panel will not collapse.
-		_switch_to_item(true, 0, true);
-	} else if (last_opened_control == p_item) {
-		// When a dock is removed by plugins, it might not have been visible, and it
-		// might have been the last_opened_control. We need to make sure to reset the last opened control.
-		last_opened_control = items[0].control;
-	}
-}
+	bottom_docks.remove_at(item_idx);
+	dock_shortcuts.remove_at(item_idx);
 
 
-void EditorBottomPanel::make_item_visible(Control *p_item, bool p_visible, bool p_ignore_lock) {
-	_switch_by_control(p_visible, p_item, p_ignore_lock);
-}
+	legacy_buttons[item_idx]->queue_free();
+	legacy_buttons.remove_at(item_idx);
 
 
-void EditorBottomPanel::move_item_to_end(Control *p_item) {
-	for (int i = 0; i < items.size(); i++) {
-		if (items[i].control == p_item) {
-			items[i].button->move_to_front();
-			SWAP(items.write[i], items.write[items.size() - 1]);
-			break;
-		}
-	}
+	remove_child(p_item);
 }
 }
 
 
-void EditorBottomPanel::hide_bottom_panel() {
-	for (int i = 0; i < items.size(); i++) {
-		if (items[i].control->is_visible()) {
-			_switch_to_item(false, i);
-			break;
-		}
-	}
-}
-
-void EditorBottomPanel::toggle_last_opened_bottom_panel() {
-	// Select by control instead of index, so that the last bottom panel is opened correctly
-	// if it's been reordered since.
-	if (last_opened_control) {
-		_switch_by_control(!last_opened_control->is_visible(), last_opened_control, true);
-	} else {
-		// Open the first panel in the list if no panel was opened this session.
-		_switch_to_item(true, 0, true);
+void EditorBottomPanel::_on_button_visibility_changed(Button *p_button, Control *p_control) {
+	int tab_index = get_tab_idx_from_control(p_control);
+	if (tab_index == -1) {
+		return;
 	}
 	}
-}
 
 
-void EditorBottomPanel::set_expanded(bool p_expanded) {
-	expand_button->set_pressed(p_expanded);
+	// Ignore the tab if the button is hidden.
+	get_tab_bar()->set_tab_hidden(tab_index, !p_button->is_visible());
 }
 }
 
 
 EditorBottomPanel::EditorBottomPanel() {
 EditorBottomPanel::EditorBottomPanel() {
-	item_vbox = memnew(VBoxContainer);
-	add_child(item_vbox);
+	get_tab_bar()->connect(SceneStringName(gui_input), callable_mp(EditorDockManager::get_singleton(), &EditorDockManager::_dock_container_gui_input).bind(this));
+	get_tab_bar()->connect("tab_changed", callable_mp(this, &EditorBottomPanel::_on_tab_changed));
+	set_tabs_position(TabPosition::POSITION_BOTTOM);
+	set_deselect_enabled(true);
 
 
 	bottom_hbox = memnew(HBoxContainer);
 	bottom_hbox = memnew(HBoxContainer);
-	bottom_hbox->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon.
-	item_vbox->add_child(bottom_hbox);
-
-	left_button = memnew(Button);
-	left_button->set_tooltip_text(TTRC("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page."));
-	left_button->set_accessibility_name(TTRC("Scroll Left"));
-	left_button->set_theme_type_variation("BottomPanelButton");
-	left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
-	left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(false));
-	bottom_hbox->add_child(left_button);
-	left_button->hide();
-
-	button_scroll = memnew(ScrollContainer);
-	button_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	button_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER);
-	button_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
-	button_scroll->get_h_scroll_bar()->connect(CoreStringName(changed), callable_mp(this, &EditorBottomPanel::_update_scroll_buttons), CONNECT_DEFERRED);
-	button_scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorBottomPanel::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED);
-	bottom_hbox->add_child(button_scroll);
-
-	right_button = memnew(Button);
-	right_button->set_tooltip_text(TTRC("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page."));
-	right_button->set_accessibility_name(TTRC("Scroll Right"));
-	right_button->set_theme_type_variation("BottomPanelButton");
-	right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
-	right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(true));
-	bottom_hbox->add_child(right_button);
-	right_button->hide();
-
-	callable_mp(this, &EditorBottomPanel::_update_scroll_buttons).call_deferred();
-
-	button_hbox = memnew(HBoxContainer);
-	button_hbox->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN);
-	button_scroll->add_child(button_hbox);
+	bottom_hbox->set_anchors_and_offsets_preset(Control::PRESET_RIGHT_WIDE);
+	bottom_hbox->set_h_grow_direction(Control::GROW_DIRECTION_END);
+	get_tab_bar()->add_child(bottom_hbox);
 
 
 	editor_toaster = memnew(EditorToaster);
 	editor_toaster = memnew(EditorToaster);
 	bottom_hbox->add_child(editor_toaster);
 	bottom_hbox->add_child(editor_toaster);
@@ -355,7 +225,7 @@ EditorBottomPanel::EditorBottomPanel() {
 	pin_button = memnew(Button);
 	pin_button = memnew(Button);
 	bottom_hbox->add_child(pin_button);
 	bottom_hbox->add_child(pin_button);
 	pin_button->hide();
 	pin_button->hide();
-	pin_button->set_theme_type_variation("FlatMenuButton");
+	pin_button->set_theme_type_variation("BottomPanelButton");
 	pin_button->set_toggle_mode(true);
 	pin_button->set_toggle_mode(true);
 	pin_button->set_tooltip_text(TTRC("Pin Bottom Panel Switching"));
 	pin_button->set_tooltip_text(TTRC("Pin Bottom Panel Switching"));
 	pin_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_pin_button_toggled));
 	pin_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_pin_button_toggled));
@@ -363,9 +233,17 @@ EditorBottomPanel::EditorBottomPanel() {
 	expand_button = memnew(Button);
 	expand_button = memnew(Button);
 	bottom_hbox->add_child(expand_button);
 	bottom_hbox->add_child(expand_button);
 	expand_button->hide();
 	expand_button->hide();
-	expand_button->set_theme_type_variation("FlatMenuButton");
+	expand_button->set_theme_type_variation("BottomPanelButton");
 	expand_button->set_toggle_mode(true);
 	expand_button->set_toggle_mode(true);
 	expand_button->set_accessibility_name(TTRC("Expand Bottom Panel"));
 	expand_button->set_accessibility_name(TTRC("Expand Bottom Panel"));
 	expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTRC("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
 	expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTRC("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
 	expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
 	expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
+
+	callable_mp(this, &EditorBottomPanel::_repaint).call_deferred();
+}
+
+EditorBottomPanel::~EditorBottomPanel() {
+	for (Button *b : legacy_buttons) {
+		memdelete(b);
+	}
 }
 }

+ 16 - 28
editor/gui/editor_bottom_panel.h

@@ -30,52 +30,38 @@
 
 
 #pragma once
 #pragma once
 
 
-#include "scene/gui/panel_container.h"
+#include "scene/gui/tab_container.h"
 
 
 class Button;
 class Button;
 class ConfigFile;
 class ConfigFile;
 class EditorToaster;
 class EditorToaster;
 class HBoxContainer;
 class HBoxContainer;
-class VBoxContainer;
-class ScrollContainer;
 
 
-class EditorBottomPanel : public PanelContainer {
-	GDCLASS(EditorBottomPanel, PanelContainer);
+class EditorBottomPanel : public TabContainer {
+	GDCLASS(EditorBottomPanel, TabContainer);
 
 
-	struct BottomPanelItem {
-		String name;
-		Control *control = nullptr;
-		Button *button = nullptr;
-	};
-
-	Vector<BottomPanelItem> items;
-	bool lock_panel_switching = false;
-
-	VBoxContainer *item_vbox = nullptr;
 	HBoxContainer *bottom_hbox = nullptr;
 	HBoxContainer *bottom_hbox = nullptr;
-	Button *left_button = nullptr;
-	Button *right_button = nullptr;
-	ScrollContainer *button_scroll = nullptr;
-	HBoxContainer *button_hbox = nullptr;
 	EditorToaster *editor_toaster = nullptr;
 	EditorToaster *editor_toaster = nullptr;
 	Button *pin_button = nullptr;
 	Button *pin_button = nullptr;
 	Button *expand_button = nullptr;
 	Button *expand_button = nullptr;
-	Control *last_opened_control = nullptr;
 
 
-	void _switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock = false);
-	void _switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock = false);
+	bool lock_panel_switching = false;
+	LocalVector<Control *> bottom_docks;
+	LocalVector<Ref<Shortcut>> dock_shortcuts;
+
+	LocalVector<Button *> legacy_buttons;
+	void _on_button_visibility_changed(Button *p_button, Control *p_control);
+
+	void _repaint();
+	void _on_tab_changed(int p_idx);
 	void _pin_button_toggled(bool p_pressed);
 	void _pin_button_toggled(bool p_pressed);
 	void _expand_button_toggled(bool p_pressed);
 	void _expand_button_toggled(bool p_pressed);
-	void _scroll(bool p_right);
-	void _update_scroll_buttons();
-	void _update_disabled_buttons();
-	void _ensure_control_visible(ObjectID p_id);
-
-	bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control);
 
 
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
 
 
+	virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
+
 public:
 public:
 	void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
 	void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
 	void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
 	void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
@@ -87,6 +73,8 @@ public:
 	void hide_bottom_panel();
 	void hide_bottom_panel();
 	void toggle_last_opened_bottom_panel();
 	void toggle_last_opened_bottom_panel();
 	void set_expanded(bool p_expanded);
 	void set_expanded(bool p_expanded);
+	void _theme_changed();
 
 
 	EditorBottomPanel();
 	EditorBottomPanel();
+	~EditorBottomPanel();
 };
 };

+ 2 - 7
editor/script/script_editor_plugin.cpp

@@ -4160,13 +4160,8 @@ void ScriptEditor::_start_find_in_files(bool with_replace) {
 	find_in_files->set_replace_text(find_in_files_dialog->get_replace_text());
 	find_in_files->set_replace_text(find_in_files_dialog->get_replace_text());
 	find_in_files->start_search();
 	find_in_files->start_search();
 
 
-	if (find_in_files_button->get_index() != find_in_files_button->get_parent()->get_child_count()) {
-		find_in_files_button->get_parent()->move_child(find_in_files_button, -1);
-	}
-	if (!find_in_files_button->is_visible()) {
-		find_in_files_button->show();
-	}
-
+	EditorNode::get_bottom_panel()->move_item_to_end(find_in_files);
+	find_in_files_button->show();
 	EditorNode::get_bottom_panel()->make_item_visible(find_in_files);
 	EditorNode::get_bottom_panel()->make_item_visible(find_in_files);
 }
 }
 
 

+ 1 - 1
editor/shader/shader_editor_plugin.cpp

@@ -921,7 +921,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
 	empty.instantiate();
 	empty.instantiate();
 	shader_tabs->add_theme_style_override(SceneStringName(panel), empty);
 	shader_tabs->add_theme_style_override(SceneStringName(panel), empty);
 
 
-	button = EditorNode::get_bottom_panel()->add_item(TTRC("Shader Editor"), window_wrapper, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTRC("Toggle Shader Editor Bottom Panel"), KeyModifierMask::ALT | Key::S));
+	EditorNode::get_bottom_panel()->add_item(TTRC("Shader Editor"), window_wrapper, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTRC("Toggle Shader Editor Bottom Panel"), KeyModifierMask::ALT | Key::S));
 
 
 	shader_create_dialog = memnew(ShaderCreateDialog);
 	shader_create_dialog = memnew(ShaderCreateDialog);
 	files_split->add_child(shader_create_dialog);
 	files_split->add_child(shader_create_dialog);

+ 0 - 1
editor/shader/shader_editor_plugin.h

@@ -88,7 +88,6 @@ class ShaderEditorPlugin : public EditorPlugin {
 	TabContainer *shader_tabs = nullptr;
 	TabContainer *shader_tabs = nullptr;
 	HBoxContainer *empty_menu = nullptr;
 	HBoxContainer *empty_menu = nullptr;
 
 
-	Button *button = nullptr;
 	MenuButton *file_menu = nullptr;
 	MenuButton *file_menu = nullptr;
 	PopupMenu *context_menu = nullptr;
 	PopupMenu *context_menu = nullptr;
 
 

+ 33 - 3
editor/themes/theme_classic.cpp

@@ -1635,13 +1635,43 @@ void ThemeClassic::populate_editor_styles(const Ref<EditorTheme> &p_theme, Edito
 
 
 		// Bottom panel.
 		// Bottom panel.
 		Ref<StyleBoxFlat> style_bottom_panel = p_config.content_panel_style->duplicate();
 		Ref<StyleBoxFlat> style_bottom_panel = p_config.content_panel_style->duplicate();
+		style_bottom_panel->set_border_width(SIDE_BOTTOM, 0);
 		style_bottom_panel->set_corner_radius_all(p_config.corner_radius * EDSCALE);
 		style_bottom_panel->set_corner_radius_all(p_config.corner_radius * EDSCALE);
+		style_bottom_panel->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
+		style_bottom_panel->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
+
+		Ref<StyleBoxFlat> style_bottom_panel_hidden = style_bottom_panel->duplicate();
+		style_bottom_panel_hidden->set_content_margin(SIDE_TOP, 0);
+
+		Ref<StyleBoxFlat> style_bottom_panel_tabbar = p_config.content_panel_style->duplicate();
+		style_bottom_panel_tabbar->set_content_margin(SIDE_TOP, 0);
+		Ref<StyleBoxFlat> style_bottom_tab = menu_transparent_style->duplicate();
+		style_bottom_tab->set_content_margin(SIDE_TOP, (p_config.increased_margin + 2) * EDSCALE);
+		style_bottom_tab->set_content_margin(SIDE_BOTTOM, (p_config.increased_margin + 2) * EDSCALE);
+
+		Ref<StyleBoxFlat> style_bottom_tab_selected = style_bottom_tab->duplicate();
+		style_bottom_tab_selected->set_bg_color(p_config.dark_color_1);
+
+		Ref<StyleBoxFlat> style_bottom_tab_hover = style_bottom_tab->duplicate();
+		style_bottom_tab_hover->set_bg_color(p_config.button_style_hover->get_bg_color());
+
 		p_theme->set_stylebox("BottomPanel", EditorStringName(EditorStyles), style_bottom_panel);
 		p_theme->set_stylebox("BottomPanel", EditorStringName(EditorStyles), style_bottom_panel);
+		p_theme->set_type_variation("BottomPanel", "TabContainer");
+		p_theme->set_stylebox(SceneStringName(panel), "BottomPanel", style_bottom_panel_hidden);
+		p_theme->set_stylebox("tabbar_background", "BottomPanel", style_bottom_panel_tabbar);
+		p_theme->set_stylebox("tab_selected", "BottomPanel", style_bottom_tab_selected);
+		p_theme->set_stylebox("tab_hovered", "BottomPanel", style_bottom_tab_hover);
+		p_theme->set_stylebox("tab_focus", "BottomPanel", menu_transparent_style);
+		p_theme->set_stylebox("tab_unselected", "BottomPanel", style_bottom_tab);
+		p_theme->set_color("font_unselected_color", "BottomPanel", p_config.font_color);
+		p_theme->set_color("font_hovered_color", "BottomPanel", p_config.font_hover_color);
+		p_theme->set_color("font_selected_color", "BottomPanel", p_config.accent_color);
+		p_theme->set_constant("tab_separation", "BottomPanel", p_config.separation_margin);
 		p_theme->set_type_variation("BottomPanelButton", "FlatMenuButton");
 		p_theme->set_type_variation("BottomPanelButton", "FlatMenuButton");
 		p_theme->set_stylebox(CoreStringName(normal), "BottomPanelButton", menu_transparent_style);
 		p_theme->set_stylebox(CoreStringName(normal), "BottomPanelButton", menu_transparent_style);
-		p_theme->set_stylebox(SceneStringName(pressed), "BottomPanelButton", menu_transparent_style);
-		p_theme->set_stylebox("hover_pressed", "BottomPanelButton", main_screen_button_hover);
-		p_theme->set_stylebox(SceneStringName(hover), "BottomPanelButton", main_screen_button_hover);
+		p_theme->set_stylebox(SceneStringName(pressed), "BottomPanelButton", style_bottom_tab_selected);
+		p_theme->set_stylebox("hover_pressed", "BottomPanelButton", style_bottom_tab_hover);
+		p_theme->set_stylebox(SceneStringName(hover), "BottomPanelButton", style_bottom_tab_hover);
 		// Don't tint the icon even when in "pressed" state.
 		// Don't tint the icon even when in "pressed" state.
 		p_theme->set_color("icon_pressed_color", "BottomPanelButton", Color(1, 1, 1, 1));
 		p_theme->set_color("icon_pressed_color", "BottomPanelButton", Color(1, 1, 1, 1));
 		Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_icon_and_font ? 1.15 : 1.0);
 		Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_icon_and_font ? 1.15 : 1.0);

+ 29 - 3
editor/themes/theme_modern.cpp

@@ -1617,8 +1617,33 @@ void ThemeModern::populate_editor_styles(const Ref<EditorTheme> &p_theme, Editor
 
 
 		// Bottom panel.
 		// Bottom panel.
 		Ref<StyleBoxFlat> style_bottom_panel = p_config.content_panel_style->duplicate();
 		Ref<StyleBoxFlat> style_bottom_panel = p_config.content_panel_style->duplicate();
+		style_bottom_panel->set_border_width(SIDE_BOTTOM, 0);
 		style_bottom_panel->set_corner_radius_all(p_config.corner_radius * EDSCALE);
 		style_bottom_panel->set_corner_radius_all(p_config.corner_radius * EDSCALE);
+		style_bottom_panel->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
+		style_bottom_panel->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
+
+		Ref<StyleBoxFlat> style_bottom_panel_hidden = style_bottom_panel->duplicate();
+		style_bottom_panel_hidden->set_content_margin(SIDE_TOP, 0);
+		Ref<StyleBoxFlat> style_bottom_panel_tabbar = p_config.content_panel_style->duplicate();
+		style_bottom_panel_tabbar->set_content_margin(SIDE_TOP, 0);
+
+		Ref<StyleBoxEmpty> style_bottom_tab = p_config.base_empty_style->duplicate();
+		style_bottom_tab->set_content_margin_individual(p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE, p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE);
+		Ref<StyleBoxFlat> bottom_panel_button_hover = p_config.flat_button_hover->duplicate();
+		bottom_panel_button_hover->set_content_margin_individual(p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE, p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE);
+
 		p_theme->set_stylebox("BottomPanel", EditorStringName(EditorStyles), style_bottom_panel);
 		p_theme->set_stylebox("BottomPanel", EditorStringName(EditorStyles), style_bottom_panel);
+		p_theme->set_type_variation("BottomPanel", "TabContainer");
+		p_theme->set_stylebox(SceneStringName(panel), "BottomPanel", style_bottom_panel_hidden);
+		p_theme->set_stylebox("tabbar_background", "BottomPanel", style_bottom_panel_tabbar);
+		p_theme->set_stylebox("tab_selected", "BottomPanel", bottom_panel_button_hover);
+		p_theme->set_stylebox("tab_hovered", "BottomPanel", bottom_panel_button_hover);
+		p_theme->set_stylebox("tab_focus", "BottomPanel", p_config.base_empty_style);
+		p_theme->set_stylebox("tab_unselected", "BottomPanel", style_bottom_tab);
+		p_theme->set_color("font_unselected_color", "BottomPanel", p_config.font_color);
+		p_theme->set_color("font_hovered_color", "BottomPanel", p_config.font_hover_color);
+		p_theme->set_color("font_selected_color", "BottomPanel", p_config.font_hover_color);
+		p_theme->set_constant("tab_separation", "BottomPanel", p_config.separation_margin);
 
 
 		p_theme->set_type_variation("BottomPanelButton", "FlatMenuButton");
 		p_theme->set_type_variation("BottomPanelButton", "FlatMenuButton");
 
 
@@ -1627,8 +1652,6 @@ void ThemeModern::populate_editor_styles(const Ref<EditorTheme> &p_theme, Editor
 		bottom_panel_button->set_content_margin_individual(p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE, p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE);
 		bottom_panel_button->set_content_margin_individual(p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE, p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE);
 		p_theme->set_stylebox(CoreStringName(normal), "BottomPanelButton", bottom_panel_button);
 		p_theme->set_stylebox(CoreStringName(normal), "BottomPanelButton", bottom_panel_button);
 
 
-		Ref<StyleBoxFlat> bottom_panel_button_hover = p_config.flat_button_hover->duplicate();
-		bottom_panel_button_hover->set_content_margin_individual(p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE, p_config.base_margin * 2 * EDSCALE, p_config.base_margin * 1.2 * EDSCALE);
 		p_theme->set_stylebox(SceneStringName(hover), "BottomPanelButton", bottom_panel_button_hover);
 		p_theme->set_stylebox(SceneStringName(hover), "BottomPanelButton", bottom_panel_button_hover);
 		p_theme->set_stylebox(SceneStringName(pressed), "BottomPanelButton", bottom_panel_button_hover);
 		p_theme->set_stylebox(SceneStringName(pressed), "BottomPanelButton", bottom_panel_button_hover);
 
 
@@ -1640,8 +1663,11 @@ void ThemeModern::populate_editor_styles(const Ref<EditorTheme> &p_theme, Editor
 		p_theme->set_color("icon_pressed_color", "BottomPanelButton", Color(1, 1, 1, 1));
 		p_theme->set_color("icon_pressed_color", "BottomPanelButton", Color(1, 1, 1, 1));
 		Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_icon_and_font ? 1.15 : 1.0);
 		Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_icon_and_font ? 1.15 : 1.0);
 		icon_hover_color.a = 1.0;
 		icon_hover_color.a = 1.0;
+		p_theme->set_color("icon_hover_color", "BottomPanel", icon_hover_color);
+		p_theme->set_color("icon_hover_pressed_color", "BottomPanel", icon_hover_color);
 		p_theme->set_color("icon_hover_color", "BottomPanelButton", icon_hover_color);
 		p_theme->set_color("icon_hover_color", "BottomPanelButton", icon_hover_color);
-		p_theme->set_color("icon_hover_pressed_color", "BottomPanelButton", icon_hover_color);
+		p_theme->set_color("icon_hover_pressed_color", "BottomPanelButton", p_config.accent_color);
+		p_theme->set_color("icon_pressed_color", "BottomPanelButton", p_config.accent_color);
 
 
 		// Audio bus.
 		// Audio bus.
 		Ref<StyleBoxFlat> audio_bus = p_config.base_style->duplicate();
 		Ref<StyleBoxFlat> audio_bus = p_config.base_style->duplicate();

+ 3 - 3
editor/version_control/version_control_editor_plugin.cpp

@@ -276,7 +276,7 @@ void VersionControlEditorPlugin::_commit() {
 
 
 	EditorVCSInterface::get_singleton()->commit(msg);
 	EditorVCSInterface::get_singleton()->commit(msg);
 
 
-	version_control_dock_button->set_pressed(false);
+	EditorNode::get_bottom_panel()->make_item_visible(version_control_dock, false);
 
 
 	commit_message->release_focus();
 	commit_message->release_focus();
 	commit_button->release_focus();
 	commit_button->release_focus();
@@ -488,7 +488,7 @@ void VersionControlEditorPlugin::_move_all(Object *p_tree) {
 void VersionControlEditorPlugin::_load_diff(Object *p_tree) {
 void VersionControlEditorPlugin::_load_diff(Object *p_tree) {
 	CHECK_PLUGIN_INITIALIZED();
 	CHECK_PLUGIN_INITIALIZED();
 
 
-	version_control_dock_button->set_pressed(true);
+	EditorNode::get_bottom_panel()->make_item_visible(version_control_dock, true, true);
 
 
 	Tree *tree = Object::cast_to<Tree>(p_tree);
 	Tree *tree = Object::cast_to<Tree>(p_tree);
 	if (tree == staged_files) {
 	if (tree == staged_files) {
@@ -910,7 +910,7 @@ void VersionControlEditorPlugin::fetch_available_vcs_plugin_names() {
 void VersionControlEditorPlugin::register_editor() {
 void VersionControlEditorPlugin::register_editor() {
 	EditorDockManager::get_singleton()->add_dock(version_commit_dock);
 	EditorDockManager::get_singleton()->add_dock(version_commit_dock);
 
 
-	version_control_dock_button = EditorNode::get_bottom_panel()->add_item(TTRC("Version Control"), version_control_dock, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_version_control_bottom_panel", TTRC("Toggle Version Control Bottom Panel")));
+	EditorNode::get_bottom_panel()->add_item(TTRC("Version Control"), version_control_dock, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_version_control_bottom_panel", TTRC("Toggle Version Control Bottom Panel")));
 
 
 	_set_vcs_ui_state(true);
 	_set_vcs_ui_state(true);
 }
 }

+ 0 - 1
editor/version_control/version_control_editor_plugin.h

@@ -130,7 +130,6 @@ private:
 	Button *commit_button = nullptr;
 	Button *commit_button = nullptr;
 
 
 	VBoxContainer *version_control_dock = nullptr;
 	VBoxContainer *version_control_dock = nullptr;
-	Button *version_control_dock_button = nullptr;
 	Label *diff_title = nullptr;
 	Label *diff_title = nullptr;
 	RichTextLabel *diff = nullptr;
 	RichTextLabel *diff = nullptr;
 	OptionButton *diff_view_type_select = nullptr;
 	OptionButton *diff_view_type_select = nullptr;