瀏覽代碼

Extract editor dock manager

kit 1 年之前
父節點
當前提交
2323f040e9

+ 716 - 0
editor/editor_dock_manager.cpp

@@ -0,0 +1,716 @@
+/**************************************************************************/
+/*  editor_dock_manager.cpp                                               */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "editor_dock_manager.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/label.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/split_container.h"
+#include "scene/gui/tab_container.h"
+#include "scene/main/window.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
+#include "editor/filesystem_dock.h"
+#include "editor/window_wrapper.h"
+
+EditorDockManager *EditorDockManager::singleton = nullptr;
+
+void DockSplitContainer::_update_visibility() {
+	if (is_updating) {
+		return;
+	}
+	is_updating = true;
+	bool any_visible = false;
+	for (int i = 0; i < 2; i++) {
+		Control *split = get_containable_child(i);
+		if (split && split->is_visible()) {
+			any_visible = true;
+			break;
+		}
+	}
+	set_visible(any_visible);
+	is_updating = false;
+}
+
+void DockSplitContainer::add_child_notify(Node *p_child) {
+	SplitContainer::add_child_notify(p_child);
+
+	Control *child_control = nullptr;
+	for (int i = 0; i < 2; i++) {
+		Control *split = get_containable_child(i);
+		if (p_child == split) {
+			child_control = split;
+			break;
+		}
+	}
+	if (!child_control) {
+		return;
+	}
+
+	child_control->connect("visibility_changed", callable_mp(this, &DockSplitContainer::_update_visibility));
+	_update_visibility();
+}
+
+void DockSplitContainer::remove_child_notify(Node *p_child) {
+	SplitContainer::remove_child_notify(p_child);
+
+	Control *child_control = nullptr;
+	for (int i = 0; i < 2; i++) {
+		Control *split = get_containable_child(i);
+		if (p_child == split) {
+			child_control = split;
+			break;
+		}
+	}
+	if (!child_control) {
+		return;
+	}
+
+	child_control->disconnect("visibility_changed", callable_mp(this, &DockSplitContainer::_update_visibility));
+	_update_visibility();
+}
+
+void EditorDockManager::_dock_select_popup_theme_changed() {
+	if (dock_float) {
+		dock_float->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("MakeFloating")));
+	}
+	if (dock_select_popup->is_layout_rtl()) {
+		dock_tab_move_left->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Forward")));
+		dock_tab_move_right->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Back")));
+	} else {
+		dock_tab_move_left->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Back")));
+		dock_tab_move_right->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Forward")));
+	}
+}
+
+void EditorDockManager::_dock_popup_exit() {
+	dock_select_rect_over_idx = -1;
+	dock_select->queue_redraw();
+}
+
+void EditorDockManager::_dock_pre_popup(int p_dock_slot) {
+	dock_popup_selected_idx = p_dock_slot;
+}
+
+void EditorDockManager::_dock_move_left() {
+	if (dock_popup_selected_idx < 0 || dock_popup_selected_idx >= DOCK_SLOT_MAX) {
+		return;
+	}
+	Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
+	Control *prev_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() - 1);
+	if (!current_ctl || !prev_ctl) {
+		return;
+	}
+	dock_slot[dock_popup_selected_idx]->move_child(current_ctl, prev_ctl->get_index(false));
+	dock_select->queue_redraw();
+	_edit_current();
+	emit_signal(SNAME("layout_changed"));
+}
+
+void EditorDockManager::_dock_move_right() {
+	if (dock_popup_selected_idx < 0 || dock_popup_selected_idx >= DOCK_SLOT_MAX) {
+		return;
+	}
+	Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
+	Control *next_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() + 1);
+	if (!current_ctl || !next_ctl) {
+		return;
+	}
+	dock_slot[dock_popup_selected_idx]->move_child(next_ctl, current_ctl->get_index(false));
+	dock_select->queue_redraw();
+	_edit_current();
+	emit_signal(SNAME("layout_changed"));
+}
+
+void EditorDockManager::_dock_select_input(const Ref<InputEvent> &p_input) {
+	Ref<InputEventMouse> me = p_input;
+
+	if (me.is_valid()) {
+		Vector2 point = me->get_position();
+
+		int nrect = -1;
+		for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+			if (dock_select_rect[i].has_point(point)) {
+				nrect = i;
+				break;
+			}
+		}
+
+		if (nrect != dock_select_rect_over_idx) {
+			dock_select->queue_redraw();
+			dock_select_rect_over_idx = nrect;
+		}
+
+		if (nrect == -1) {
+			return;
+		}
+
+		Ref<InputEventMouseButton> mb = me;
+
+		if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed() && dock_popup_selected_idx != nrect) {
+			dock_slot[nrect]->move_tab_from_tab_container(dock_slot[dock_popup_selected_idx], dock_slot[dock_popup_selected_idx]->get_current_tab(), dock_slot[nrect]->get_tab_count());
+
+			if (dock_slot[dock_popup_selected_idx]->get_tab_count() == 0) {
+				dock_slot[dock_popup_selected_idx]->hide();
+			} else {
+				dock_slot[dock_popup_selected_idx]->set_current_tab(0);
+			}
+
+			dock_popup_selected_idx = nrect;
+			dock_slot[nrect]->show();
+			dock_select->queue_redraw();
+
+			update_dock_slots_visibility(true);
+
+			_edit_current();
+			emit_signal(SNAME("layout_changed"));
+		}
+	}
+}
+
+void EditorDockManager::_dock_select_draw() {
+	Size2 s = dock_select->get_size();
+	s.y /= 2.0;
+	s.x /= 6.0;
+
+	Color used = Color(0.6, 0.6, 0.6, 0.8);
+	Color used_selected = Color(0.8, 0.8, 0.8, 0.8);
+	Color tab_selected = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));
+	Color unused = used;
+	unused.a = 0.4;
+	Color unusable = unused;
+	unusable.a = 0.1;
+
+	Rect2 unr(s.x * 2, 0, s.x * 2, s.y * 2);
+	unr.position += Vector2(2, 5);
+	unr.size -= Vector2(4, 7);
+
+	dock_select->draw_rect(unr, unusable);
+
+	dock_tab_move_left->set_disabled(true);
+	dock_tab_move_right->set_disabled(true);
+
+	if (dock_popup_selected_idx != -1 && dock_slot[dock_popup_selected_idx]->get_tab_count()) {
+		dock_tab_move_left->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() == 0);
+		dock_tab_move_right->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() >= dock_slot[dock_popup_selected_idx]->get_tab_count() - 1);
+	}
+
+	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+		Vector2 ofs;
+
+		switch (i) {
+			case DOCK_SLOT_LEFT_UL: {
+			} break;
+			case DOCK_SLOT_LEFT_BL: {
+				ofs.y += s.y;
+			} break;
+			case DOCK_SLOT_LEFT_UR: {
+				ofs.x += s.x;
+			} break;
+			case DOCK_SLOT_LEFT_BR: {
+				ofs += s;
+			} break;
+			case DOCK_SLOT_RIGHT_UL: {
+				ofs.x += s.x * 4;
+			} break;
+			case DOCK_SLOT_RIGHT_BL: {
+				ofs.x += s.x * 4;
+				ofs.y += s.y;
+
+			} break;
+			case DOCK_SLOT_RIGHT_UR: {
+				ofs.x += s.x * 4;
+				ofs.x += s.x;
+
+			} break;
+			case DOCK_SLOT_RIGHT_BR: {
+				ofs.x += s.x * 4;
+				ofs += s;
+
+			} break;
+		}
+
+		Rect2 r(ofs, s);
+		dock_select_rect[i] = r;
+		r.position += Vector2(2, 5);
+		r.size -= Vector2(4, 7);
+
+		if (i == dock_select_rect_over_idx) {
+			dock_select->draw_rect(r, used_selected);
+		} else if (dock_slot[i]->get_tab_count() == 0) {
+			dock_select->draw_rect(r, unused);
+		} else {
+			dock_select->draw_rect(r, used);
+		}
+
+		for (int j = 0; j < MIN(3, dock_slot[i]->get_tab_count()); j++) {
+			int xofs = (r.size.width / 3) * j;
+			Color c = used;
+			if (i == dock_popup_selected_idx && (dock_slot[i]->get_current_tab() > 3 || dock_slot[i]->get_current_tab() == j)) {
+				c = tab_selected;
+			}
+			dock_select->draw_rect(Rect2(2 + ofs.x + xofs, ofs.y, r.size.width / 3 - 1, 3), c);
+		}
+	}
+}
+
+void EditorDockManager::_dock_split_dragged(int p_offset) {
+	EditorNode::get_singleton()->save_editor_layout_delayed();
+}
+
+void EditorDockManager::_dock_tab_changed(int p_tab) {
+	// Update visibility but don't set current tab.
+	update_dock_slots_visibility(true);
+}
+
+void EditorDockManager::_edit_current() {
+	EditorNode::get_singleton()->edit_current();
+}
+
+void EditorDockManager::_dock_floating_close_request(WindowWrapper *p_wrapper) {
+	int dock_slot_num = p_wrapper->get_meta("dock_slot");
+	int dock_slot_index = p_wrapper->get_meta("dock_index");
+
+	// Give back the dock to the original owner.
+	Control *dock = p_wrapper->release_wrapped_control();
+
+	int target_index = MIN(dock_slot_index, dock_slot[dock_slot_num]->get_tab_count());
+	dock_slot[dock_slot_num]->add_child(dock);
+	dock_slot[dock_slot_num]->move_child(dock, target_index);
+	dock_slot[dock_slot_num]->set_current_tab(target_index);
+
+	floating_docks.erase(p_wrapper);
+	p_wrapper->queue_free();
+
+	update_dock_slots_visibility(true);
+
+	_edit_current();
+}
+
+void EditorDockManager::_dock_make_selected_float() {
+	Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control();
+	_dock_make_float(dock, dock_popup_selected_idx);
+
+	dock_select_popup->hide();
+	_edit_current();
+}
+
+void EditorDockManager::_dock_make_float(Control *p_dock, int p_slot_index, bool p_show_window) {
+	ERR_FAIL_NULL(p_dock);
+
+	Size2 borders = Size2(4, 4) * EDSCALE;
+	// Remember size and position before removing it from the main window.
+	Size2 dock_size = p_dock->get_size() + borders * 2;
+	Point2 dock_screen_pos = p_dock->get_screen_position();
+
+	int dock_index = p_dock->get_index() - 1;
+	dock_slot[p_slot_index]->remove_child(p_dock);
+
+	WindowWrapper *wrapper = memnew(WindowWrapper);
+	wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), p_dock->get_name()));
+	wrapper->set_margins_enabled(true);
+
+	EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);
+
+	wrapper->set_wrapped_control(p_dock);
+	wrapper->set_meta("dock_slot", p_slot_index);
+	wrapper->set_meta("dock_index", dock_index);
+	wrapper->set_meta("dock_name", p_dock->get_name().operator String());
+	p_dock->show();
+
+	wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_dock_floating_close_request).bind(wrapper));
+
+	dock_select_popup->hide();
+
+	if (p_show_window) {
+		wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());
+	}
+
+	update_dock_slots_visibility(true);
+
+	floating_docks.push_back(wrapper);
+
+	_edit_current();
+}
+
+void EditorDockManager::_restore_floating_dock(const Dictionary &p_dock_dump, Control *p_dock, int p_slot_index) {
+	WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(p_dock);
+	if (!wrapper) {
+		_dock_make_float(p_dock, p_slot_index, false);
+		wrapper = floating_docks[floating_docks.size() - 1];
+	}
+
+	wrapper->restore_window_from_saved_position(
+			p_dock_dump.get("window_rect", Rect2i()),
+			p_dock_dump.get("window_screen", -1),
+			p_dock_dump.get("window_screen_rect", Rect2i()));
+}
+
+void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {
+	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+		String names;
+		for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
+			String name = dock_slot[i]->get_tab_control(j)->get_name();
+			if (!names.is_empty()) {
+				names += ",";
+			}
+			names += name;
+		}
+
+		String config_key = "dock_" + itos(i + 1);
+
+		if (p_layout->has_section_key(p_section, config_key)) {
+			p_layout->erase_section_key(p_section, config_key);
+		}
+
+		if (!names.is_empty()) {
+			p_layout->set_value(p_section, config_key, names);
+		}
+
+		int selected_tab_idx = dock_slot[i]->get_current_tab();
+		if (selected_tab_idx >= 0) {
+			p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx);
+		}
+	}
+
+	Dictionary floating_docks_dump;
+
+	for (WindowWrapper *wrapper : floating_docks) {
+		Control *dock = wrapper->get_wrapped_control();
+
+		Dictionary dock_dump;
+		dock_dump["window_rect"] = wrapper->get_window_rect();
+
+		int screen = wrapper->get_window_screen();
+		dock_dump["window_screen"] = wrapper->get_window_screen();
+		dock_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
+
+		String name = dock->get_name();
+		floating_docks_dump[name] = dock_dump;
+
+		int dock_slot_id = wrapper->get_meta("dock_slot");
+		String config_key = "dock_" + itos(dock_slot_id + 1);
+
+		String names = p_layout->get_value(p_section, config_key, "");
+		if (names.is_empty()) {
+			names = name;
+		} else {
+			names += "," + name;
+		}
+		p_layout->set_value(p_section, config_key, names);
+	}
+
+	p_layout->set_value(p_section, "dock_floating", floating_docks_dump);
+
+	for (int i = 0; i < vsplits.size(); i++) {
+		if (vsplits[i]->is_visible_in_tree()) {
+			p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
+		}
+	}
+
+	for (int i = 0; i < hsplits.size(); i++) {
+		p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset());
+	}
+
+	FileSystemDock::get_singleton()->save_layout_to_config(p_layout, p_section);
+}
+
+void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
+	Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());
+
+	bool restore_window_on_load = EDITOR_GET("interface/multi_window/restore_windows_on_load");
+
+	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+		if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) {
+			continue;
+		}
+
+		Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(",");
+
+		for (int j = names.size() - 1; j >= 0; j--) {
+			String name = names[j];
+
+			// FIXME: Find it, in a horribly inefficient way.
+			int atidx = -1;
+			Control *node = nullptr;
+			for (int k = 0; k < DOCK_SLOT_MAX; k++) {
+				if (!dock_slot[k]->has_node(name)) {
+					continue;
+				}
+				node = Object::cast_to<Control>(dock_slot[k]->get_node(name));
+				if (!node) {
+					continue;
+				}
+				atidx = k;
+				break;
+			}
+
+			if (atidx == -1) {
+				// Try floating docks.
+				for (WindowWrapper *wrapper : floating_docks) {
+					if (wrapper->get_meta("dock_name") == name) {
+						if (restore_window_on_load && floating_docks_dump.has(name)) {
+							_restore_floating_dock(floating_docks_dump[name], wrapper, i);
+						} else {
+							atidx = wrapper->get_meta("dock_slot");
+							node = wrapper->get_wrapped_control();
+							wrapper->set_window_enabled(false);
+						}
+						break;
+					}
+				}
+			}
+			if (!node) {
+				// Well, it's not anywhere.
+				continue;
+			}
+
+			if (atidx == i) {
+				dock_slot[i]->move_child(node, 0);
+			} else if (atidx != -1) {
+				dock_slot[i]->set_block_signals(true);
+				dock_slot[atidx]->set_block_signals(true);
+				dock_slot[i]->move_tab_from_tab_container(dock_slot[atidx], dock_slot[atidx]->get_tab_idx_from_control(node), 0);
+				dock_slot[i]->set_block_signals(false);
+				dock_slot[atidx]->set_block_signals(false);
+			}
+
+			WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(node);
+			if (restore_window_on_load && floating_docks_dump.has(name)) {
+				if (!dock_slot[i]->is_tab_hidden(dock_slot[i]->get_tab_idx_from_control(node))) {
+					_restore_floating_dock(floating_docks_dump[name], node, i);
+				}
+			} else if (wrapper) {
+				wrapper->set_window_enabled(false);
+			}
+		}
+
+		if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {
+			continue;
+		}
+
+		int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx");
+		if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) {
+			callable_mp(dock_slot[i], &TabContainer::set_current_tab).call_deferred(selected_tab_idx);
+		}
+	}
+
+	for (int i = 0; i < vsplits.size(); i++) {
+		if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {
+			continue;
+		}
+
+		int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
+		vsplits[i]->set_split_offset(ofs);
+	}
+
+	for (int i = 0; i < hsplits.size(); i++) {
+		if (!p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {
+			continue;
+		}
+		int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
+		hsplits[i]->set_split_offset(ofs);
+	}
+
+	update_dock_slots_visibility(false);
+
+	FileSystemDock::get_singleton()->load_layout_from_config(p_layout, p_section);
+}
+
+void EditorDockManager::update_dock_slots_visibility(bool p_keep_selected_tabs) {
+	if (!docks_visible) {
+		for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+			dock_slot[i]->hide();
+		}
+	} else {
+		for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+			int first_tab_visible = -1;
+			for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
+				if (!dock_slot[i]->is_tab_hidden(j)) {
+					first_tab_visible = j;
+					break;
+				}
+			}
+			if (first_tab_visible >= 0) {
+				dock_slot[i]->show();
+				if (p_keep_selected_tabs) {
+					int current_tab = dock_slot[i]->get_current_tab();
+					if (dock_slot[i]->is_tab_hidden(current_tab)) {
+						dock_slot[i]->set_block_signals(true);
+						dock_slot[i]->select_next_available();
+						dock_slot[i]->set_block_signals(false);
+					}
+				} else {
+					dock_slot[i]->set_block_signals(true);
+					dock_slot[i]->set_current_tab(first_tab_visible);
+					dock_slot[i]->set_block_signals(false);
+				}
+			} else {
+				dock_slot[i]->hide();
+			}
+		}
+	}
+}
+
+void EditorDockManager::close_all_floating_docks() {
+	for (WindowWrapper *wrapper : floating_docks) {
+		wrapper->set_window_enabled(false);
+	}
+}
+
+void EditorDockManager::add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name) {
+	ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
+	dock_slot[p_slot]->add_child(p_control);
+	if (!p_name.is_empty()) {
+		dock_slot[p_slot]->set_tab_title(dock_slot[p_slot]->get_tab_idx_from_control(p_control), p_name);
+	}
+}
+
+void EditorDockManager::remove_control_from_dock(Control *p_control) {
+	// If the dock is floating, close it first.
+	for (WindowWrapper *wrapper : floating_docks) {
+		if (p_control == wrapper->get_wrapped_control()) {
+			wrapper->set_window_enabled(false);
+			break;
+		}
+	}
+
+	Control *dock = nullptr;
+	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+		if (p_control->get_parent() == dock_slot[i]) {
+			dock = dock_slot[i];
+			break;
+		}
+	}
+
+	ERR_FAIL_NULL_MSG(dock, "Control is not in a dock.");
+
+	dock->remove_child(p_control);
+	update_dock_slots_visibility();
+}
+
+void EditorDockManager::set_docks_visible(bool p_show) {
+	docks_visible = p_show;
+	update_dock_slots_visibility(true);
+}
+
+bool EditorDockManager::are_docks_visible() const {
+	return docks_visible;
+}
+
+void EditorDockManager::add_vsplit(DockSplitContainer *p_split) {
+	vsplits.push_back(p_split);
+	p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));
+}
+
+void EditorDockManager::add_hsplit(DockSplitContainer *p_split) {
+	hsplits.push_back(p_split);
+	p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));
+}
+
+void EditorDockManager::register_dock_slot(DockSlot p_dock_slot, TabContainer *p_tab_container) {
+	ERR_FAIL_NULL(p_tab_container);
+	ERR_FAIL_INDEX(p_dock_slot, DOCK_SLOT_MAX);
+
+	dock_slot[p_dock_slot] = p_tab_container;
+
+	p_tab_container->set_custom_minimum_size(Size2(170, 0) * EDSCALE);
+	p_tab_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+	p_tab_container->set_popup(dock_select_popup);
+	p_tab_container->connect("pre_popup_pressed", callable_mp(this, &EditorDockManager::_dock_pre_popup).bind(p_dock_slot));
+	p_tab_container->set_drag_to_rearrange_enabled(true);
+	p_tab_container->set_tabs_rearrange_group(1);
+	p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_dock_tab_changed));
+	p_tab_container->set_use_hidden_tabs_for_min_size(true);
+}
+
+int EditorDockManager::get_vsplit_count() const {
+	return vsplits.size();
+}
+
+void EditorDockManager::_bind_methods() {
+	ADD_SIGNAL(MethodInfo("layout_changed"));
+}
+
+EditorDockManager::EditorDockManager() {
+	singleton = this;
+
+	dock_select_popup = memnew(PopupPanel);
+	EditorNode::get_singleton()->get_gui_base()->add_child(dock_select_popup);
+	VBoxContainer *dock_vb = memnew(VBoxContainer);
+	dock_select_popup->add_child(dock_vb);
+	dock_select_popup->connect("theme_changed", callable_mp(this, &EditorDockManager::_dock_select_popup_theme_changed));
+
+	HBoxContainer *dock_hb = memnew(HBoxContainer);
+	dock_tab_move_left = memnew(Button);
+	dock_tab_move_left->set_flat(true);
+	dock_tab_move_left->set_focus_mode(Control::FOCUS_NONE);
+	dock_tab_move_left->connect("pressed", callable_mp(this, &EditorDockManager::_dock_move_left));
+	dock_hb->add_child(dock_tab_move_left);
+
+	Label *dock_label = memnew(Label);
+	dock_label->set_text(TTR("Dock Position"));
+	dock_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+	dock_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
+	dock_hb->add_child(dock_label);
+
+	dock_tab_move_right = memnew(Button);
+	dock_tab_move_right->set_flat(true);
+	dock_tab_move_right->set_focus_mode(Control::FOCUS_NONE);
+	dock_tab_move_right->connect("pressed", callable_mp(this, &EditorDockManager::_dock_move_right));
+
+	dock_hb->add_child(dock_tab_move_right);
+	dock_vb->add_child(dock_hb);
+
+	dock_select = memnew(Control);
+	dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
+	dock_select->connect("gui_input", callable_mp(this, &EditorDockManager::_dock_select_input));
+	dock_select->connect("draw", callable_mp(this, &EditorDockManager::_dock_select_draw));
+	dock_select->connect("mouse_exited", callable_mp(this, &EditorDockManager::_dock_popup_exit));
+	dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+	dock_vb->add_child(dock_select);
+
+	if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) {
+		dock_float = memnew(Button);
+		dock_float->set_text(TTR("Make Floating"));
+		dock_float->set_focus_mode(Control::FOCUS_NONE);
+		dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+		dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float));
+
+		dock_vb->add_child(dock_float);
+	}
+
+	dock_select_popup->reset_size();
+}

+ 135 - 0
editor/editor_dock_manager.h

@@ -0,0 +1,135 @@
+/**************************************************************************/
+/*  editor_dock_manager.h                                                 */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef EDITOR_DOCK_MANAGER_H
+#define EDITOR_DOCK_MANAGER_H
+
+#include "scene/gui/split_container.h"
+
+class Button;
+class ConfigFile;
+class Control;
+class PopupPanel;
+class TabContainer;
+class WindowWrapper;
+
+class DockSplitContainer : public SplitContainer {
+	GDCLASS(DockSplitContainer, SplitContainer);
+
+private:
+	bool is_updating = false;
+
+protected:
+	void _update_visibility();
+
+	virtual void add_child_notify(Node *p_child) override;
+	virtual void remove_child_notify(Node *p_child) override;
+};
+
+class EditorDockManager : public Object {
+	GDCLASS(EditorDockManager, Object);
+
+public:
+	enum DockSlot {
+		DOCK_SLOT_LEFT_UL,
+		DOCK_SLOT_LEFT_BL,
+		DOCK_SLOT_LEFT_UR,
+		DOCK_SLOT_LEFT_BR,
+		DOCK_SLOT_RIGHT_UL,
+		DOCK_SLOT_RIGHT_BL,
+		DOCK_SLOT_RIGHT_UR,
+		DOCK_SLOT_RIGHT_BR,
+		DOCK_SLOT_MAX
+	};
+
+private:
+	static EditorDockManager *singleton;
+
+	// To access splits easily by index.
+	Vector<DockSplitContainer *> vsplits;
+	Vector<DockSplitContainer *> hsplits;
+
+	Vector<WindowWrapper *> floating_docks;
+	TabContainer *dock_slot[DOCK_SLOT_MAX];
+	bool docks_visible = true;
+
+	PopupPanel *dock_select_popup = nullptr;
+	Button *dock_float = nullptr;
+	Button *dock_tab_move_left = nullptr;
+	Button *dock_tab_move_right = nullptr;
+	Control *dock_select = nullptr;
+	Rect2 dock_select_rect[DOCK_SLOT_MAX];
+	int dock_select_rect_over_idx = -1;
+	int dock_popup_selected_idx = -1;
+
+	void _dock_select_popup_theme_changed();
+	void _dock_popup_exit();
+	void _dock_pre_popup(int p_dock_slot);
+	void _dock_move_left();
+	void _dock_move_right();
+	void _dock_select_input(const Ref<InputEvent> &p_input);
+	void _dock_select_draw();
+	void _dock_split_dragged(int p_offset);
+
+	void _dock_tab_changed(int p_tab);
+	void _edit_current();
+
+	void _dock_floating_close_request(WindowWrapper *p_wrapper);
+	void _dock_make_selected_float();
+	void _dock_make_float(Control *p_control, int p_slot_index, bool p_show_window = true);
+	void _restore_floating_dock(const Dictionary &p_dock_dump, Control *p_wrapper, int p_slot_index);
+
+protected:
+	static void _bind_methods();
+
+public:
+	static EditorDockManager *get_singleton() { return singleton; }
+
+	void add_vsplit(DockSplitContainer *p_split);
+	void add_hsplit(DockSplitContainer *p_split);
+	void register_dock_slot(DockSlot p_dock_slot, TabContainer *p_tab_container);
+	int get_vsplit_count() const;
+
+	void save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const;
+	void load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section);
+	void update_dock_slots_visibility(bool p_keep_selected_tabs = false);
+
+	void close_all_floating_docks();
+
+	void set_docks_visible(bool p_show);
+	bool are_docks_visible() const;
+
+	void add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name = "");
+	void remove_control_from_dock(Control *p_control);
+
+	EditorDockManager();
+};
+
+#endif // EDITOR_DOCK_MANAGER_H

+ 89 - 711
editor/editor_node.cpp

@@ -79,6 +79,7 @@
 #include "editor/editor_build_profile.h"
 #include "editor/editor_command_palette.h"
 #include "editor/editor_data.h"
+#include "editor/editor_dock_manager.h"
 #include "editor/editor_feature_profile.h"
 #include "editor/editor_folding.h"
 #include "editor/editor_help.h"
@@ -507,14 +508,6 @@ void EditorNode::_update_theme(bool p_skip_creation) {
 	distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
 	bottom_panel_raise->set_icon(theme->get_icon(SNAME("ExpandBottomDock"), EditorStringName(EditorIcons)));
 
-	if (gui_base->is_layout_rtl()) {
-		dock_tab_move_left->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
-		dock_tab_move_right->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
-	} else {
-		dock_tab_move_left->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
-		dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
-	}
-
 	help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)));
 	help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
 	help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
@@ -2095,7 +2088,7 @@ void EditorNode::_dialog_action(String p_file) {
 				return;
 			}
 
-			_save_docks_to_config(config, p_file);
+			editor_dock_manager->save_docks_to_config(config, p_file);
 
 			config->save(EditorSettings::get_singleton()->get_editor_layouts_config());
 
@@ -4738,240 +4731,6 @@ void EditorNode::_copy_warning(const String &p_str) {
 	DisplayServer::get_singleton()->clipboard_set(warning->get_text());
 }
 
-void EditorNode::_dock_floating_close_request(WindowWrapper *p_wrapper) {
-	int dock_slot_num = p_wrapper->get_meta("dock_slot");
-	int dock_slot_index = p_wrapper->get_meta("dock_index");
-
-	// Give back the dock to the original owner.
-	Control *dock = p_wrapper->release_wrapped_control();
-
-	int target_index = MIN(dock_slot_index, dock_slot[dock_slot_num]->get_tab_count());
-	dock_slot[dock_slot_num]->add_child(dock);
-	dock_slot[dock_slot_num]->move_child(dock, target_index);
-	dock_slot[dock_slot_num]->set_current_tab(target_index);
-
-	floating_docks.erase(p_wrapper);
-	p_wrapper->queue_free();
-
-	_update_dock_slots_visibility(true);
-
-	_edit_current();
-}
-
-void EditorNode::_dock_make_selected_float() {
-	Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control();
-	_dock_make_float(dock, dock_popup_selected_idx);
-
-	dock_select_popup->hide();
-	_edit_current();
-}
-
-void EditorNode::_dock_make_float(Control *p_dock, int p_slot_index, bool p_show_window) {
-	ERR_FAIL_NULL(p_dock);
-
-	Size2 borders = Size2(4, 4) * EDSCALE;
-	// Remember size and position before removing it from the main window.
-	Size2 dock_size = p_dock->get_size() + borders * 2;
-	Point2 dock_screen_pos = p_dock->get_screen_position();
-
-	int dock_index = p_dock->get_index() - 1;
-	dock_slot[p_slot_index]->remove_child(p_dock);
-
-	WindowWrapper *wrapper = memnew(WindowWrapper);
-	wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), p_dock->get_name()));
-	wrapper->set_margins_enabled(true);
-
-	gui_base->add_child(wrapper);
-
-	wrapper->set_wrapped_control(p_dock);
-	wrapper->set_meta("dock_slot", p_slot_index);
-	wrapper->set_meta("dock_index", dock_index);
-	wrapper->set_meta("dock_name", p_dock->get_name().operator String());
-	p_dock->show();
-
-	wrapper->connect("window_close_requested", callable_mp(this, &EditorNode::_dock_floating_close_request).bind(wrapper));
-
-	dock_select_popup->hide();
-
-	if (p_show_window) {
-		wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), get_window()->get_current_screen());
-	}
-
-	_update_dock_slots_visibility(true);
-
-	floating_docks.push_back(wrapper);
-
-	_edit_current();
-}
-
-void EditorNode::_dock_select_input(const Ref<InputEvent> &p_input) {
-	Ref<InputEventMouse> me = p_input;
-
-	if (me.is_valid()) {
-		Vector2 point = me->get_position();
-
-		int nrect = -1;
-		for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-			if (dock_select_rect[i].has_point(point)) {
-				nrect = i;
-				break;
-			}
-		}
-
-		if (nrect != dock_select_rect_over_idx) {
-			dock_select->queue_redraw();
-			dock_select_rect_over_idx = nrect;
-		}
-
-		if (nrect == -1) {
-			return;
-		}
-
-		Ref<InputEventMouseButton> mb = me;
-
-		if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed() && dock_popup_selected_idx != nrect) {
-			dock_slot[nrect]->move_tab_from_tab_container(dock_slot[dock_popup_selected_idx], dock_slot[dock_popup_selected_idx]->get_current_tab(), dock_slot[nrect]->get_tab_count());
-
-			if (dock_slot[dock_popup_selected_idx]->get_tab_count() == 0) {
-				dock_slot[dock_popup_selected_idx]->hide();
-			} else {
-				dock_slot[dock_popup_selected_idx]->set_current_tab(0);
-			}
-
-			dock_popup_selected_idx = nrect;
-			dock_slot[nrect]->show();
-			dock_select->queue_redraw();
-
-			_update_dock_slots_visibility(true);
-
-			_edit_current();
-			_save_editor_layout();
-		}
-	}
-}
-
-void EditorNode::_dock_popup_exit() {
-	dock_select_rect_over_idx = -1;
-	dock_select->queue_redraw();
-}
-
-void EditorNode::_dock_pre_popup(int p_which) {
-	dock_popup_selected_idx = p_which;
-}
-
-void EditorNode::_dock_move_left() {
-	if (dock_popup_selected_idx < 0 || dock_popup_selected_idx >= DOCK_SLOT_MAX) {
-		return;
-	}
-	Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
-	Control *prev_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() - 1);
-	if (!current_ctl || !prev_ctl) {
-		return;
-	}
-	dock_slot[dock_popup_selected_idx]->move_child(current_ctl, prev_ctl->get_index(false));
-	dock_select->queue_redraw();
-	_edit_current();
-	_save_editor_layout();
-}
-
-void EditorNode::_dock_move_right() {
-	Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
-	Control *next_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() + 1);
-	if (!current_ctl || !next_ctl) {
-		return;
-	}
-	dock_slot[dock_popup_selected_idx]->move_child(next_ctl, current_ctl->get_index(false));
-	dock_select->queue_redraw();
-	_edit_current();
-	_save_editor_layout();
-}
-
-void EditorNode::_dock_select_draw() {
-	Size2 s = dock_select->get_size();
-	s.y /= 2.0;
-	s.x /= 6.0;
-
-	Color used = Color(0.6, 0.6, 0.6, 0.8);
-	Color used_selected = Color(0.8, 0.8, 0.8, 0.8);
-	Color tab_selected = theme->get_color(SNAME("mono_color"), EditorStringName(Editor));
-	Color unused = used;
-	unused.a = 0.4;
-	Color unusable = unused;
-	unusable.a = 0.1;
-
-	Rect2 unr(s.x * 2, 0, s.x * 2, s.y * 2);
-	unr.position += Vector2(2, 5);
-	unr.size -= Vector2(4, 7);
-
-	dock_select->draw_rect(unr, unusable);
-
-	dock_tab_move_left->set_disabled(true);
-	dock_tab_move_right->set_disabled(true);
-
-	if (dock_popup_selected_idx != -1 && dock_slot[dock_popup_selected_idx]->get_tab_count()) {
-		dock_tab_move_left->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() == 0);
-		dock_tab_move_right->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() >= dock_slot[dock_popup_selected_idx]->get_tab_count() - 1);
-	}
-
-	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-		Vector2 ofs;
-
-		switch (i) {
-			case DOCK_SLOT_LEFT_UL: {
-			} break;
-			case DOCK_SLOT_LEFT_BL: {
-				ofs.y += s.y;
-			} break;
-			case DOCK_SLOT_LEFT_UR: {
-				ofs.x += s.x;
-			} break;
-			case DOCK_SLOT_LEFT_BR: {
-				ofs += s;
-			} break;
-			case DOCK_SLOT_RIGHT_UL: {
-				ofs.x += s.x * 4;
-			} break;
-			case DOCK_SLOT_RIGHT_BL: {
-				ofs.x += s.x * 4;
-				ofs.y += s.y;
-
-			} break;
-			case DOCK_SLOT_RIGHT_UR: {
-				ofs.x += s.x * 4;
-				ofs.x += s.x;
-
-			} break;
-			case DOCK_SLOT_RIGHT_BR: {
-				ofs.x += s.x * 4;
-				ofs += s;
-
-			} break;
-		}
-
-		Rect2 r(ofs, s);
-		dock_select_rect[i] = r;
-		r.position += Vector2(2, 5);
-		r.size -= Vector2(4, 7);
-
-		if (i == dock_select_rect_over_idx) {
-			dock_select->draw_rect(r, used_selected);
-		} else if (dock_slot[i]->get_tab_count() == 0) {
-			dock_select->draw_rect(r, unused);
-		} else {
-			dock_select->draw_rect(r, used);
-		}
-
-		for (int j = 0; j < MIN(3, dock_slot[i]->get_tab_count()); j++) {
-			int xofs = (r.size.width / 3) * j;
-			Color c = used;
-			if (i == dock_popup_selected_idx && (dock_slot[i]->get_current_tab() > 3 || dock_slot[i]->get_current_tab() == j)) {
-				c = tab_selected;
-			}
-			dock_select->draw_rect(Rect2(2 + ofs.x + xofs, ofs.y, r.size.width / 3 - 1, 3), c);
-		}
-	}
-}
-
 void EditorNode::_save_editor_layout() {
 	if (waiting_for_first_scan) {
 		return; // Scanning, do not touch docks.
@@ -4981,7 +4740,7 @@ void EditorNode::_save_editor_layout() {
 	// Load and amend existing config if it exists.
 	config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
 
-	_save_docks_to_config(config, "docks");
+	editor_dock_manager->save_docks_to_config(config, "docks");
 	_save_open_scenes_to_config(config);
 	_save_central_editor_layout_to_config(config);
 	editor_data.get_plugin_window_layout(config);
@@ -4989,85 +4748,6 @@ void EditorNode::_save_editor_layout() {
 	config->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
 }
 
-void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) {
-	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-		String names;
-		for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
-			String name = dock_slot[i]->get_tab_control(j)->get_name();
-			if (!names.is_empty()) {
-				names += ",";
-			}
-			names += name;
-		}
-
-		String config_key = "dock_" + itos(i + 1);
-
-		if (p_layout->has_section_key(p_section, config_key)) {
-			p_layout->erase_section_key(p_section, config_key);
-		}
-
-		if (!names.is_empty()) {
-			p_layout->set_value(p_section, config_key, names);
-		}
-
-		int selected_tab_idx = dock_slot[i]->get_current_tab();
-		if (selected_tab_idx >= 0) {
-			p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx);
-		}
-	}
-
-	Dictionary floating_docks_dump;
-
-	for (WindowWrapper *wrapper : floating_docks) {
-		Control *dock = wrapper->get_wrapped_control();
-
-		Dictionary dock_dump;
-		dock_dump["window_rect"] = wrapper->get_window_rect();
-
-		int screen = wrapper->get_window_screen();
-		dock_dump["window_screen"] = wrapper->get_window_screen();
-		dock_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
-
-		String name = dock->get_name();
-		floating_docks_dump[name] = dock_dump;
-
-		int dock_slot_id = wrapper->get_meta("dock_slot");
-		String config_key = "dock_" + itos(dock_slot_id + 1);
-
-		String names = p_layout->get_value(p_section, config_key, "");
-		if (names.is_empty()) {
-			names = name;
-		} else {
-			names += "," + name;
-		}
-		p_layout->set_value(p_section, config_key, names);
-	}
-
-	p_layout->set_value(p_section, "dock_floating", floating_docks_dump);
-
-	for (int i = 0; i < vsplits.size(); i++) {
-		if (vsplits[i]->is_visible_in_tree()) {
-			p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
-		}
-	}
-
-	for (int i = 0; i < hsplits.size(); i++) {
-		p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset());
-	}
-
-	// Save FileSystemDock state.
-
-	p_layout->set_value(p_section, "dock_filesystem_h_split_offset", FileSystemDock::get_singleton()->get_h_split_offset());
-	p_layout->set_value(p_section, "dock_filesystem_v_split_offset", FileSystemDock::get_singleton()->get_v_split_offset());
-	p_layout->set_value(p_section, "dock_filesystem_display_mode", FileSystemDock::get_singleton()->get_display_mode());
-	p_layout->set_value(p_section, "dock_filesystem_file_sort", FileSystemDock::get_singleton()->get_file_sort());
-	p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", FileSystemDock::get_singleton()->get_file_list_display_mode());
-	PackedStringArray selected_files = FileSystemDock::get_singleton()->get_selected_paths();
-	p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files);
-	Vector<String> uncollapsed_paths = FileSystemDock::get_singleton()->get_uncollapsed_paths();
-	p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths);
-}
-
 void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout) {
 	PackedStringArray scenes;
 	for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
@@ -5087,10 +4767,6 @@ void EditorNode::save_editor_layout_delayed() {
 	editor_layout_save_delay_timer->start();
 }
 
-void EditorNode::_dock_split_dragged(int ofs) {
-	editor_layout_save_delay_timer->start();
-}
-
 void EditorNode::_load_editor_layout() {
 	Ref<ConfigFile> config;
 	config.instantiate();
@@ -5113,227 +4789,13 @@ void EditorNode::_load_editor_layout() {
 		return;
 	}
 
-	_load_docks_from_config(config, "docks");
+	editor_dock_manager->load_docks_from_config(config, "docks");
 	_load_open_scenes_from_config(config);
 	_load_central_editor_layout_from_config(config);
 
 	editor_data.set_plugin_window_layout(config);
 }
 
-void EditorNode::_update_dock_slots_visibility(bool p_keep_selected_tabs) {
-	if (!docks_visible) {
-		for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-			dock_slot[i]->hide();
-		}
-
-		for (int i = 0; i < vsplits.size(); i++) {
-			vsplits[i]->hide();
-		}
-
-		right_hsplit->hide();
-	} else {
-		for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-			int first_tab_visible = -1;
-			for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
-				if (!dock_slot[i]->is_tab_hidden(j)) {
-					first_tab_visible = j;
-					break;
-				}
-			}
-			if (first_tab_visible >= 0) {
-				dock_slot[i]->show();
-				if (p_keep_selected_tabs) {
-					int current_tab = dock_slot[i]->get_current_tab();
-					if (dock_slot[i]->is_tab_hidden(current_tab)) {
-						dock_slot[i]->set_block_signals(true);
-						dock_slot[i]->select_next_available();
-						dock_slot[i]->set_block_signals(false);
-					}
-				} else {
-					dock_slot[i]->set_block_signals(true);
-					dock_slot[i]->set_current_tab(first_tab_visible);
-					dock_slot[i]->set_block_signals(false);
-				}
-			} else {
-				dock_slot[i]->hide();
-			}
-		}
-
-		for (int i = 0; i < vsplits.size(); i++) {
-			bool in_use = dock_slot[i * 2 + 0]->is_visible() || dock_slot[i * 2 + 1]->is_visible();
-			vsplits[i]->set_visible(in_use);
-		}
-
-		right_hsplit->set_visible(right_l_vsplit->is_visible() || right_r_vsplit->is_visible());
-	}
-}
-
-void EditorNode::_dock_tab_changed(int p_tab) {
-	// Update visibility but don't set current tab.
-	_update_dock_slots_visibility(true);
-}
-
-void EditorNode::_restore_floating_dock(const Dictionary &p_dock_dump, Control *p_dock, int p_slot_index) {
-	WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(p_dock);
-	if (!wrapper) {
-		_dock_make_float(p_dock, p_slot_index, false);
-		wrapper = floating_docks[floating_docks.size() - 1];
-	}
-
-	wrapper->restore_window_from_saved_position(
-			p_dock_dump.get("window_rect", Rect2i()),
-			p_dock_dump.get("window_screen", -1),
-			p_dock_dump.get("window_screen_rect", Rect2i()));
-}
-
-void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
-	Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());
-
-	bool restore_window_on_load = EDITOR_GET("interface/multi_window/restore_windows_on_load");
-
-	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-		if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) {
-			continue;
-		}
-
-		Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(",");
-
-		for (int j = names.size() - 1; j >= 0; j--) {
-			const String &name = names[j];
-
-			// FIXME: Find it, in a horribly inefficient way.
-			int atidx = -1;
-			Control *node = nullptr;
-			for (int k = 0; k < DOCK_SLOT_MAX; k++) {
-				if (!dock_slot[k]->has_node(name)) {
-					continue;
-				}
-				node = Object::cast_to<Control>(dock_slot[k]->get_node(name));
-				if (!node) {
-					continue;
-				}
-				atidx = k;
-				break;
-			}
-
-			if (atidx == -1) {
-				// Try floating docks.
-				for (WindowWrapper *wrapper : floating_docks) {
-					if (wrapper->get_meta("dock_name") == name) {
-						if (restore_window_on_load && floating_docks_dump.has(name)) {
-							_restore_floating_dock(floating_docks_dump[name], wrapper, i);
-						} else {
-							atidx = wrapper->get_meta("dock_slot");
-							node = wrapper->get_wrapped_control();
-							wrapper->set_window_enabled(false);
-						}
-						break;
-					}
-				}
-			}
-			if (!node) {
-				// Well, it's not anywhere.
-				continue;
-			}
-
-			if (atidx == i) {
-				dock_slot[i]->move_child(node, 0);
-			} else if (atidx != -1) {
-				dock_slot[i]->move_tab_from_tab_container(dock_slot[atidx], dock_slot[atidx]->get_tab_idx_from_control(node), 0);
-			}
-
-			WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(node);
-			if (restore_window_on_load && floating_docks_dump.has(name)) {
-				if (!dock_slot[i]->is_tab_hidden(dock_slot[i]->get_tab_idx_from_control(node))) {
-					_restore_floating_dock(floating_docks_dump[name], node, i);
-				}
-			} else if (wrapper) {
-				wrapper->set_window_enabled(false);
-			}
-		}
-
-		if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {
-			continue;
-		}
-
-		int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx");
-		if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) {
-			callable_mp(dock_slot[i], &TabContainer::set_current_tab).call_deferred(selected_tab_idx);
-		}
-	}
-
-	for (int i = 0; i < vsplits.size(); i++) {
-		if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {
-			continue;
-		}
-
-		int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
-		vsplits[i]->set_split_offset(ofs);
-	}
-
-	for (int i = 0; i < hsplits.size(); i++) {
-		if (!p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {
-			continue;
-		}
-		int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
-		hsplits[i]->set_split_offset(ofs);
-	}
-
-	_update_dock_slots_visibility(false);
-
-	// FileSystemDock.
-
-	if (p_layout->has_section_key(p_section, "dock_filesystem_h_split_offset")) {
-		int fs_h_split_ofs = p_layout->get_value(p_section, "dock_filesystem_h_split_offset");
-		FileSystemDock::get_singleton()->set_h_split_offset(fs_h_split_ofs);
-	}
-
-	if (p_layout->has_section_key(p_section, "dock_filesystem_v_split_offset")) {
-		int fs_v_split_ofs = p_layout->get_value(p_section, "dock_filesystem_v_split_offset");
-		FileSystemDock::get_singleton()->set_v_split_offset(fs_v_split_ofs);
-	}
-
-	if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) {
-		FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode")));
-		FileSystemDock::get_singleton()->set_display_mode(dock_filesystem_display_mode);
-	}
-
-	if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) {
-		FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort")));
-		FileSystemDock::get_singleton()->set_file_sort(dock_filesystem_file_sort);
-	}
-
-	if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) {
-		FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode")));
-		FileSystemDock::get_singleton()->set_file_list_display_mode(dock_filesystem_file_list_display_mode);
-	}
-
-	if (p_layout->has_section_key(p_section, "dock_filesystem_selected_paths")) {
-		PackedStringArray dock_filesystem_selected_paths = p_layout->get_value(p_section, "dock_filesystem_selected_paths");
-		for (int i = 0; i < dock_filesystem_selected_paths.size(); i++) {
-			FileSystemDock::get_singleton()->select_file(dock_filesystem_selected_paths[i]);
-		}
-	}
-
-	// Restore collapsed state of FileSystemDock.
-	PackedStringArray uncollapsed_tis;
-	if (p_layout->has_section_key(p_section, "dock_filesystem_uncollapsed_paths")) {
-		uncollapsed_tis = p_layout->get_value(p_section, "dock_filesystem_uncollapsed_paths");
-	} else {
-		uncollapsed_tis = { "res://" };
-	}
-
-	if (!uncollapsed_tis.is_empty()) {
-		for (int i = 0; i < uncollapsed_tis.size(); i++) {
-			TreeItem *uncollapsed_ti = FileSystemDock::get_singleton()->get_tree_control()->get_item_with_metadata(uncollapsed_tis[i], 0);
-			if (uncollapsed_ti) {
-				uncollapsed_ti->set_collapsed(false);
-			}
-		}
-		FileSystemDock::get_singleton()->get_tree_control()->queue_redraw();
-	}
-}
-
 void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) {
 	// Bottom panel.
 
@@ -5582,7 +5044,7 @@ void EditorNode::_layout_menu_option(int p_id) {
 			layout_dialog->popup_centered();
 		} break;
 		case SETTINGS_LAYOUT_DEFAULT: {
-			_load_docks_from_config(default_layout, "docks");
+			editor_dock_manager->load_docks_from_config(default_layout, "docks");
 			_save_editor_layout();
 		} break;
 		default: {
@@ -5593,7 +5055,7 @@ void EditorNode::_layout_menu_option(int p_id) {
 				return; // No config.
 			}
 
-			_load_docks_from_config(config, editor_layouts->get_item_text(p_id));
+			editor_dock_manager->load_docks_from_config(config, editor_layouts->get_item_text(p_id));
 			_save_editor_layout();
 		}
 	}
@@ -5803,15 +5265,6 @@ void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) {
 	}
 }
 
-void EditorNode::set_docks_visible(bool p_show) {
-	docks_visible = p_show;
-	_update_dock_slots_visibility(true);
-}
-
-bool EditorNode::get_docks_visible() const {
-	return docks_visible;
-}
-
 void EditorNode::_toggle_distraction_free_mode() {
 	if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
 		int screen = -1;
@@ -5838,11 +5291,11 @@ void EditorNode::set_distraction_free_mode(bool p_enter) {
 	distraction_free->set_pressed(p_enter);
 
 	if (p_enter) {
-		if (docks_visible) {
-			set_docks_visible(false);
+		if (editor_dock_manager->are_docks_visible()) {
+			editor_dock_manager->set_docks_visible(false);
 		}
 	} else {
-		set_docks_visible(true);
+		editor_dock_manager->set_docks_visible(true);
 	}
 }
 
@@ -5850,35 +5303,6 @@ bool EditorNode::is_distraction_free_mode_enabled() const {
 	return distraction_free->is_pressed();
 }
 
-void EditorNode::add_control_to_dock(DockSlot p_slot, Control *p_control) {
-	ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
-	dock_slot[p_slot]->add_child(p_control);
-	_update_dock_slots_visibility();
-}
-
-void EditorNode::remove_control_from_dock(Control *p_control) {
-	// If the dock is floating, close it first.
-	for (WindowWrapper *wrapper : floating_docks) {
-		if (p_control == wrapper->get_wrapped_control()) {
-			wrapper->set_window_enabled(false);
-			break;
-		}
-	}
-
-	Control *dock = nullptr;
-	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-		if (p_control->get_parent() == dock_slot[i]) {
-			dock = dock_slot[i];
-			break;
-		}
-	}
-
-	ERR_FAIL_NULL_MSG(dock, "Control was not in dock.");
-
-	dock->remove_child(p_control);
-	_update_dock_slots_visibility();
-}
-
 Variant EditorNode::drag_resource(const Ref<Resource> &p_res, Control *p_from) {
 	Control *drag_control = memnew(Control);
 	TextureRect *drag_preview = memnew(TextureRect);
@@ -6632,9 +6056,7 @@ void EditorNode::_resource_loaded(Ref<Resource> p_resource, const String &p_path
 void EditorNode::_feature_profile_changed() {
 	Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile();
 	// FIXME: Close all floating docks to avoid crash.
-	for (WindowWrapper *wrapper : floating_docks) {
-		wrapper->set_window_enabled(false);
-	}
+	editor_dock_manager->close_all_floating_docks();
 	TabContainer *import_tabs = cast_to<TabContainer>(ImportDock::get_singleton()->get_parent());
 	TabContainer *node_tabs = cast_to<TabContainer>(NodeDock::get_singleton()->get_parent());
 	TabContainer *fs_tabs = cast_to<TabContainer>(FileSystemDock::get_singleton()->get_parent());
@@ -6669,7 +6091,7 @@ void EditorNode::_feature_profile_changed() {
 		}
 	}
 
-	_update_dock_slots_visibility();
+	editor_dock_manager->update_dock_slots_visibility();
 }
 
 void EditorNode::_bind_methods() {
@@ -7079,127 +6501,96 @@ EditorNode::EditorNode() {
 	title_bar = memnew(EditorTitleBar);
 	main_vbox->add_child(title_bar);
 
-	left_l_hsplit = memnew(HSplitContainer);
+	left_l_hsplit = memnew(DockSplitContainer);
+	left_l_hsplit->set_name("DockHSplitLeftL");
 	main_vbox->add_child(left_l_hsplit);
 
 	left_l_hsplit->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 
-	left_l_vsplit = memnew(VSplitContainer);
+	left_l_vsplit = memnew(DockSplitContainer);
+	left_l_vsplit->set_name("DockVSplitLeftL");
+	left_l_vsplit->set_vertical(true);
 	left_l_hsplit->add_child(left_l_vsplit);
-	dock_slot[DOCK_SLOT_LEFT_UL] = memnew(TabContainer);
-	left_l_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_UL]);
-	dock_slot[DOCK_SLOT_LEFT_BL] = memnew(TabContainer);
-	left_l_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_BL]);
 
-	left_r_hsplit = memnew(HSplitContainer);
+	TabContainer *dock_slot[EditorDockManager::DOCK_SLOT_MAX];
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_UL] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_UL]->set_name("DockSlotLeftUL");
+	left_l_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_LEFT_UL]);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_BL] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_BL]->set_name("DockSlotLeftBL");
+	left_l_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_LEFT_BL]);
+
+	left_r_hsplit = memnew(DockSplitContainer);
+	left_r_hsplit->set_name("DockHSplitLeftR");
 	left_l_hsplit->add_child(left_r_hsplit);
-	left_r_vsplit = memnew(VSplitContainer);
+	left_r_vsplit = memnew(DockSplitContainer);
+	left_r_vsplit->set_name("DockVSplitLeftR");
+	left_r_vsplit->set_vertical(true);
 	left_r_hsplit->add_child(left_r_vsplit);
-	dock_slot[DOCK_SLOT_LEFT_UR] = memnew(TabContainer);
-	left_r_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_UR]);
-	dock_slot[DOCK_SLOT_LEFT_BR] = memnew(TabContainer);
-	left_r_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_BR]);
-
-	main_hsplit = memnew(HSplitContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_UR] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_UR]->set_name("DockSlotLeftUR");
+	left_r_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_LEFT_UR]);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_BR] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_LEFT_BR]->set_name("DockSlotLeftBR");
+	left_r_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_LEFT_BR]);
+
+	main_hsplit = memnew(DockSplitContainer);
+	main_hsplit->set_name("DockHSplitMain");
 	left_r_hsplit->add_child(main_hsplit);
 	VBoxContainer *center_vb = memnew(VBoxContainer);
 	main_hsplit->add_child(center_vb);
+
 	center_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 
-	center_split = memnew(VSplitContainer);
+	center_split = memnew(DockSplitContainer);
+	center_split->set_name("DockVSplitCenter");
+	center_split->set_vertical(true);
 	center_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	center_split->set_collapsed(false);
 	center_vb->add_child(center_split);
 
-	right_hsplit = memnew(HSplitContainer);
+	right_hsplit = memnew(DockSplitContainer);
+	right_hsplit->set_name("DockHSplitRight");
 	main_hsplit->add_child(right_hsplit);
 
-	right_l_vsplit = memnew(VSplitContainer);
+	right_l_vsplit = memnew(DockSplitContainer);
+	right_l_vsplit->set_name("DockVSplitRightL");
+	right_l_vsplit->set_vertical(true);
 	right_hsplit->add_child(right_l_vsplit);
-	dock_slot[DOCK_SLOT_RIGHT_UL] = memnew(TabContainer);
-	right_l_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_UL]);
-	dock_slot[DOCK_SLOT_RIGHT_BL] = memnew(TabContainer);
-	right_l_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_BL]);
-
-	right_r_vsplit = memnew(VSplitContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_UL] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_UL]->set_name("DockSlotRightUL");
+	right_l_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_UL]);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BL] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BL]->set_name("DockSlotRightBL");
+	right_l_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BL]);
+
+	right_r_vsplit = memnew(DockSplitContainer);
+	right_r_vsplit->set_name("DockVSplitRightR");
+	right_r_vsplit->set_vertical(true);
 	right_hsplit->add_child(right_r_vsplit);
-	dock_slot[DOCK_SLOT_RIGHT_UR] = memnew(TabContainer);
-	right_r_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_UR]);
-	dock_slot[DOCK_SLOT_RIGHT_BR] = memnew(TabContainer);
-	right_r_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_BR]);
-
-	// Store them for easier access.
-	vsplits.push_back(left_l_vsplit);
-	vsplits.push_back(left_r_vsplit);
-	vsplits.push_back(right_l_vsplit);
-	vsplits.push_back(right_r_vsplit);
-
-	hsplits.push_back(left_l_hsplit);
-	hsplits.push_back(left_r_hsplit);
-	hsplits.push_back(main_hsplit);
-	hsplits.push_back(right_hsplit);
-
-	for (int i = 0; i < vsplits.size(); i++) {
-		vsplits[i]->connect("dragged", callable_mp(this, &EditorNode::_dock_split_dragged));
-		hsplits[i]->connect("dragged", callable_mp(this, &EditorNode::_dock_split_dragged));
-	}
-
-	dock_select_popup = memnew(PopupPanel);
-	gui_base->add_child(dock_select_popup);
-	VBoxContainer *dock_vb = memnew(VBoxContainer);
-	dock_select_popup->add_child(dock_vb);
-
-	HBoxContainer *dock_hb = memnew(HBoxContainer);
-	dock_tab_move_left = memnew(Button);
-	dock_tab_move_left->set_flat(true);
-	dock_tab_move_left->set_focus_mode(Control::FOCUS_NONE);
-	dock_tab_move_left->connect("pressed", callable_mp(this, &EditorNode::_dock_move_left));
-	dock_hb->add_child(dock_tab_move_left);
-
-	Label *dock_label = memnew(Label);
-	dock_label->set_text(TTR("Dock Position"));
-	dock_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	dock_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
-	dock_hb->add_child(dock_label);
-
-	dock_tab_move_right = memnew(Button);
-	dock_tab_move_right->set_flat(true);
-	dock_tab_move_right->set_focus_mode(Control::FOCUS_NONE);
-	dock_tab_move_right->connect("pressed", callable_mp(this, &EditorNode::_dock_move_right));
-
-	dock_hb->add_child(dock_tab_move_right);
-	dock_vb->add_child(dock_hb);
-
-	dock_select = memnew(Control);
-	dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
-	dock_select->connect("gui_input", callable_mp(this, &EditorNode::_dock_select_input));
-	dock_select->connect("draw", callable_mp(this, &EditorNode::_dock_select_draw));
-	dock_select->connect("mouse_exited", callable_mp(this, &EditorNode::_dock_popup_exit));
-	dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-	dock_vb->add_child(dock_select);
-
-	if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) {
-		dock_float = memnew(Button);
-		dock_float->set_icon(theme->get_icon("MakeFloating", EditorStringName(EditorIcons)));
-		dock_float->set_text(TTR("Make Floating"));
-		dock_float->set_focus_mode(Control::FOCUS_NONE);
-		dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
-		dock_float->connect("pressed", callable_mp(this, &EditorNode::_dock_make_selected_float));
-
-		dock_vb->add_child(dock_float);
-	}
-
-	dock_select_popup->reset_size();
-
-	for (int i = 0; i < DOCK_SLOT_MAX; i++) {
-		dock_slot[i]->set_custom_minimum_size(Size2(170, 0) * EDSCALE);
-		dock_slot[i]->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-		dock_slot[i]->set_popup(dock_select_popup);
-		dock_slot[i]->connect("pre_popup_pressed", callable_mp(this, &EditorNode::_dock_pre_popup).bind(i));
-		dock_slot[i]->set_drag_to_rearrange_enabled(true);
-		dock_slot[i]->set_tabs_rearrange_group(1);
-		dock_slot[i]->connect("tab_changed", callable_mp(this, &EditorNode::_dock_tab_changed));
-		dock_slot[i]->set_use_hidden_tabs_for_min_size(true);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_UR] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_UR]->set_name("DockSlotRightUR");
+	right_r_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_UR]);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BR] = memnew(TabContainer);
+	dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BR]->set_name("DockSlotRightBR");
+	right_r_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BR]);
+
+	editor_dock_manager = memnew(EditorDockManager);
+	editor_dock_manager->connect("layout_changed", callable_mp(this, &EditorNode::_save_editor_layout));
+
+	// Save the splits for easier access.
+	editor_dock_manager->add_vsplit(left_l_vsplit);
+	editor_dock_manager->add_vsplit(left_r_vsplit);
+	editor_dock_manager->add_vsplit(right_l_vsplit);
+	editor_dock_manager->add_vsplit(right_r_vsplit);
+
+	editor_dock_manager->add_hsplit(left_l_hsplit);
+	editor_dock_manager->add_hsplit(left_r_hsplit);
+	editor_dock_manager->add_hsplit(main_hsplit);
+	editor_dock_manager->add_hsplit(right_hsplit);
+
+	for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {
+		editor_dock_manager->register_dock_slot((EditorDockManager::DockSlot)i, dock_slot[i]);
 	}
 
 	editor_layout_save_delay_timer = memnew(Timer);
@@ -7642,37 +7033,22 @@ EditorNode::EditorNode() {
 	history_dock = memnew(HistoryDock);
 
 	// Scene: Top left.
-	dock_slot[DOCK_SLOT_LEFT_UR]->add_child(SceneTreeDock::get_singleton());
-	dock_slot[DOCK_SLOT_LEFT_UR]->set_tab_title(dock_slot[DOCK_SLOT_LEFT_UR]->get_tab_idx_from_control(SceneTreeDock::get_singleton()), TTR("Scene"));
+	editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_LEFT_UR, SceneTreeDock::get_singleton(), TTR("Scene"));
 
 	// Import: Top left, behind Scene.
-	dock_slot[DOCK_SLOT_LEFT_UR]->add_child(ImportDock::get_singleton());
-	dock_slot[DOCK_SLOT_LEFT_UR]->set_tab_title(dock_slot[DOCK_SLOT_LEFT_UR]->get_tab_idx_from_control(ImportDock::get_singleton()), TTR("Import"));
+	editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_LEFT_UR, ImportDock::get_singleton(), TTR("Import"));
 
 	// FileSystem: Bottom left.
-	dock_slot[DOCK_SLOT_LEFT_BR]->add_child(FileSystemDock::get_singleton());
-	dock_slot[DOCK_SLOT_LEFT_BR]->set_tab_title(dock_slot[DOCK_SLOT_LEFT_BR]->get_tab_idx_from_control(FileSystemDock::get_singleton()), TTR("FileSystem"));
+	editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_LEFT_BR, FileSystemDock::get_singleton(), TTR("FileSystem"));
 
 	// Inspector: Full height right.
-	dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(InspectorDock::get_singleton());
-	dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(dock_slot[DOCK_SLOT_RIGHT_UL]->get_tab_idx_from_control(InspectorDock::get_singleton()), TTR("Inspector"));
+	editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_RIGHT_UL, InspectorDock::get_singleton(), TTR("Inspector"));
 
 	// Node: Full height right, behind Inspector.
-	dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(NodeDock::get_singleton());
-	dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(dock_slot[DOCK_SLOT_RIGHT_UL]->get_tab_idx_from_control(NodeDock::get_singleton()), TTR("Node"));
+	editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_RIGHT_UL, NodeDock::get_singleton(), TTR("Node"));
 
 	// History: Full height right, behind Node.
-	dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(history_dock);
-	dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(dock_slot[DOCK_SLOT_RIGHT_UL]->get_tab_idx_from_control(history_dock), TTR("History"));
-
-	// Hide unused dock slots and vsplits.
-	dock_slot[DOCK_SLOT_LEFT_UL]->hide();
-	dock_slot[DOCK_SLOT_LEFT_BL]->hide();
-	dock_slot[DOCK_SLOT_RIGHT_BL]->hide();
-	dock_slot[DOCK_SLOT_RIGHT_UR]->hide();
-	dock_slot[DOCK_SLOT_RIGHT_BR]->hide();
-	left_l_vsplit->hide();
-	right_r_vsplit->hide();
+	editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_RIGHT_UL, history_dock, TTR("History"));
 
 	// Add some offsets to left_r and main hsplits to make LEFT_R and RIGHT_L docks wider than minsize.
 	left_r_hsplit->set_split_offset(270 * EDSCALE);
@@ -7687,7 +7063,8 @@ EditorNode::EditorNode() {
 	default_layout->set_value(docks_section, "dock_4", "FileSystem");
 	default_layout->set_value(docks_section, "dock_5", "Inspector,Node,History");
 
-	for (int i = 0; i < vsplits.size(); i++) {
+	// There are 4 vsplits and 4 hsplits.
+	for (int i = 0; i < editor_dock_manager->get_vsplit_count(); i++) {
 		default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0);
 	}
 	default_layout->set_value(docks_section, "dock_hsplit_1", 0);
@@ -8119,6 +7496,7 @@ EditorNode::~EditorNode() {
 	memdelete(editor_plugins_force_input_forwarding);
 	memdelete(progress_hb);
 	memdelete(surface_upgrade_tool);
+	memdelete(editor_dock_manager);
 
 	EditorSettings::destroy();
 	EditorColorMap::finish();

+ 12 - 59
editor/editor_node.h

@@ -73,10 +73,12 @@ class AudioStreamPreviewGenerator;
 class BackgroundProgress;
 class DependencyEditor;
 class DependencyErrorDialog;
+class DockSplitContainer;
 class DynamicFontImportSettingsDialog;
 class EditorAbout;
 class EditorBuildProfileManager;
 class EditorCommandPalette;
+class EditorDockManager;
 class EditorExport;
 class EditorExtensionManager;
 class EditorFeatureProfileManager;
@@ -121,18 +123,6 @@ class EditorNode : public Node {
 	GDCLASS(EditorNode, Node);
 
 public:
-	enum DockSlot {
-		DOCK_SLOT_LEFT_UL,
-		DOCK_SLOT_LEFT_BL,
-		DOCK_SLOT_LEFT_UR,
-		DOCK_SLOT_LEFT_BR,
-		DOCK_SLOT_RIGHT_UL,
-		DOCK_SLOT_RIGHT_BL,
-		DOCK_SLOT_RIGHT_UR,
-		DOCK_SLOT_RIGHT_BR,
-		DOCK_SLOT_MAX
-	};
-
 	enum EditorTable {
 		EDITOR_2D = 0,
 		EDITOR_3D,
@@ -310,18 +300,15 @@ private:
 	String renderer_request;
 
 	// Split containers.
-	HSplitContainer *left_l_hsplit = nullptr;
-	VSplitContainer *left_l_vsplit = nullptr;
-	HSplitContainer *left_r_hsplit = nullptr;
-	VSplitContainer *left_r_vsplit = nullptr;
-	HSplitContainer *main_hsplit = nullptr;
-	HSplitContainer *right_hsplit = nullptr;
-	VSplitContainer *right_l_vsplit = nullptr;
-	VSplitContainer *right_r_vsplit = nullptr;
-	VSplitContainer *center_split = nullptr;
-	// To access those easily by index.
-	Vector<VSplitContainer *> vsplits;
-	Vector<HSplitContainer *> hsplits;
+	DockSplitContainer *left_l_hsplit = nullptr;
+	DockSplitContainer *left_l_vsplit = nullptr;
+	DockSplitContainer *left_r_hsplit = nullptr;
+	DockSplitContainer *left_r_vsplit = nullptr;
+	DockSplitContainer *main_hsplit = nullptr;
+	DockSplitContainer *right_hsplit = nullptr;
+	DockSplitContainer *right_l_vsplit = nullptr;
+	DockSplitContainer *right_r_vsplit = nullptr;
+	DockSplitContainer *center_split = nullptr;
 
 	// Main tabs.
 	EditorSceneTabs *scene_tabs = nullptr;
@@ -426,20 +413,8 @@ private:
 	Button *new_inherited_button = nullptr;
 	String open_import_request;
 
-	Vector<WindowWrapper *> floating_docks;
-
-	Button *dock_float = nullptr;
-	Button *dock_tab_move_left = nullptr;
-	Button *dock_tab_move_right = nullptr;
-	Control *dock_select = nullptr;
-	PopupPanel *dock_select_popup = nullptr;
-	Rect2 dock_select_rect[DOCK_SLOT_MAX];
-	TabContainer *dock_slot[DOCK_SLOT_MAX];
+	EditorDockManager *editor_dock_manager = nullptr;
 	Timer *editor_layout_save_delay_timer = nullptr;
-	bool docks_visible = true;
-	int dock_popup_selected_idx = -1;
-	int dock_select_rect_over_idx = -1;
-
 	Button *distraction_free = nullptr;
 
 	Vector<BottomPanelItem> bottom_panel_items;
@@ -634,17 +609,6 @@ private:
 
 	bool _find_scene_in_use(Node *p_node, const String &p_path) const;
 
-	void _dock_select_input(const Ref<InputEvent> &p_input);
-	void _dock_move_left();
-	void _dock_move_right();
-	void _dock_select_draw();
-	void _dock_pre_popup(int p_which);
-	void _dock_split_dragged(int ofs);
-	void _dock_popup_exit();
-	void _dock_floating_close_request(WindowWrapper *p_wrapper);
-	void _dock_make_selected_float();
-	void _dock_make_float(Control *p_control, int p_slot_index, bool p_show_window = true);
-
 	void _proceed_closing_scene_tabs();
 	bool _is_closing_editor() const;
 
@@ -655,11 +619,6 @@ private:
 
 	void _save_editor_layout();
 	void _load_editor_layout();
-	void _save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section);
-	void _restore_floating_dock(const Dictionary &p_dock_dump, Control *p_wrapper, int p_slot_index);
-	void _load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section);
-	void _update_dock_slots_visibility(bool p_keep_selected_tabs = false);
-	void _dock_tab_changed(int p_tab);
 
 	void _save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file);
 	void _load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file);
@@ -772,15 +731,9 @@ public:
 
 	void new_inherited_scene() { _menu_option_confirm(FILE_NEW_INHERITED_SCENE, false); }
 
-	void set_docks_visible(bool p_show);
-	bool get_docks_visible() const;
-
 	void set_distraction_free_mode(bool p_enter);
 	bool is_distraction_free_mode_enabled() const;
 
-	void add_control_to_dock(DockSlot p_slot, Control *p_control);
-	void remove_control_from_dock(Control *p_control);
-
 	void set_addon_plugin_enabled(const String &p_addon, bool p_enabled, bool p_config_changed = false);
 	bool is_addon_plugin_enabled(const String &p_addon) const;
 

+ 3 - 2
editor/editor_plugin.cpp

@@ -31,6 +31,7 @@
 #include "editor_plugin.h"
 
 #include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_dock_manager.h"
 #include "editor/editor_file_system.h"
 #include "editor/editor_inspector.h"
 #include "editor/editor_interface.h"
@@ -84,12 +85,12 @@ Button *EditorPlugin::add_control_to_bottom_panel(Control *p_control, const Stri
 
 void EditorPlugin::add_control_to_dock(DockSlot p_slot, Control *p_control) {
 	ERR_FAIL_NULL(p_control);
-	EditorNode::get_singleton()->add_control_to_dock(EditorNode::DockSlot(p_slot), p_control);
+	EditorDockManager::get_singleton()->add_control_to_dock(EditorDockManager::DockSlot(p_slot), p_control);
 }
 
 void EditorPlugin::remove_control_from_docks(Control *p_control) {
 	ERR_FAIL_NULL(p_control);
-	EditorNode::get_singleton()->remove_control_from_dock(p_control);
+	EditorDockManager::get_singleton()->remove_control_from_dock(p_control);
 }
 
 void EditorPlugin::remove_control_from_bottom_panel(Control *p_control) {

+ 64 - 0
editor/filesystem_dock.cpp

@@ -3665,6 +3665,70 @@ void FileSystemDock::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("display_mode_changed"));
 }
 
+void FileSystemDock::save_layout_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {
+	p_layout->set_value(p_section, "dock_filesystem_h_split_offset", get_h_split_offset());
+	p_layout->set_value(p_section, "dock_filesystem_v_split_offset", get_v_split_offset());
+	p_layout->set_value(p_section, "dock_filesystem_display_mode", get_display_mode());
+	p_layout->set_value(p_section, "dock_filesystem_file_sort", get_file_sort());
+	p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", get_file_list_display_mode());
+	PackedStringArray selected_files = get_selected_paths();
+	p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files);
+	Vector<String> uncollapsed_paths = get_uncollapsed_paths();
+	p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths);
+}
+
+void FileSystemDock::load_layout_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
+	if (p_layout->has_section_key(p_section, "dock_filesystem_h_split_offset")) {
+		int fs_h_split_ofs = p_layout->get_value(p_section, "dock_filesystem_h_split_offset");
+		set_h_split_offset(fs_h_split_ofs);
+	}
+
+	if (p_layout->has_section_key(p_section, "dock_filesystem_v_split_offset")) {
+		int fs_v_split_ofs = p_layout->get_value(p_section, "dock_filesystem_v_split_offset");
+		set_v_split_offset(fs_v_split_ofs);
+	}
+
+	if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) {
+		DisplayMode dock_filesystem_display_mode = DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode")));
+		set_display_mode(dock_filesystem_display_mode);
+	}
+
+	if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) {
+		FileSortOption dock_filesystem_file_sort = FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort")));
+		set_file_sort(dock_filesystem_file_sort);
+	}
+
+	if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) {
+		FileListDisplayMode dock_filesystem_file_list_display_mode = FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode")));
+		set_file_list_display_mode(dock_filesystem_file_list_display_mode);
+	}
+
+	if (p_layout->has_section_key(p_section, "dock_filesystem_selected_paths")) {
+		PackedStringArray dock_filesystem_selected_paths = p_layout->get_value(p_section, "dock_filesystem_selected_paths");
+		for (int i = 0; i < dock_filesystem_selected_paths.size(); i++) {
+			select_file(dock_filesystem_selected_paths[i]);
+		}
+	}
+
+	// Restore collapsed state.
+	PackedStringArray uncollapsed_tis;
+	if (p_layout->has_section_key(p_section, "dock_filesystem_uncollapsed_paths")) {
+		uncollapsed_tis = p_layout->get_value(p_section, "dock_filesystem_uncollapsed_paths");
+	} else {
+		uncollapsed_tis = { "res://" };
+	}
+
+	if (!uncollapsed_tis.is_empty()) {
+		for (int i = 0; i < uncollapsed_tis.size(); i++) {
+			TreeItem *uncollapsed_ti = get_tree_control()->get_item_with_metadata(uncollapsed_tis[i], 0);
+			if (uncollapsed_ti) {
+				uncollapsed_ti->set_collapsed(false);
+			}
+		}
+		get_tree_control()->queue_redraw();
+	}
+}
+
 FileSystemDock::FileSystemDock() {
 	singleton = this;
 	set_name("FileSystem");

+ 6 - 3
editor/filesystem_dock.h

@@ -394,13 +394,13 @@ public:
 	void select_file(const String &p_file);
 
 	void set_display_mode(DisplayMode p_display_mode);
-	DisplayMode get_display_mode() { return display_mode; }
+	DisplayMode get_display_mode() const { return display_mode; }
 
 	void set_file_sort(FileSortOption p_file_sort);
-	FileSortOption get_file_sort() { return file_sort; }
+	FileSortOption get_file_sort() const { return file_sort; }
 
 	void set_file_list_display_mode(FileListDisplayMode p_mode);
-	FileListDisplayMode get_file_list_display_mode() { return file_list_display_mode; };
+	FileListDisplayMode get_file_list_display_mode() const { return file_list_display_mode; };
 
 	Tree *get_tree_control() { return tree; }
 
@@ -408,6 +408,9 @@ public:
 	void remove_resource_tooltip_plugin(const Ref<EditorResourceTooltipPlugin> &p_plugin);
 	Control *create_tooltip_for_path(const String &p_path) const;
 
+	void save_layout_to_config(Ref<ConfigFile> p_layout, const String &p_section) const;
+	void load_layout_from_config(Ref<ConfigFile> p_layout, const String &p_section);
+
 	FileSystemDock();
 	~FileSystemDock();
 };

+ 3 - 2
editor/plugins/version_control_editor_plugin.cpp

@@ -33,6 +33,7 @@
 #include "core/config/project_settings.h"
 #include "core/os/keyboard.h"
 #include "core/os/time.h"
+#include "editor/editor_dock_manager.h"
 #include "editor/editor_file_system.h"
 #include "editor/editor_interface.h"
 #include "editor/editor_node.h"
@@ -909,7 +910,7 @@ void VersionControlEditorPlugin::fetch_available_vcs_plugin_names() {
 }
 
 void VersionControlEditorPlugin::register_editor() {
-	EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock);
+	EditorDockManager::get_singleton()->add_control_to_dock(EditorDockManager::DOCK_SLOT_RIGHT_UL, version_commit_dock);
 
 	version_control_dock_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Version Control"), version_control_dock);
 
@@ -929,7 +930,7 @@ void VersionControlEditorPlugin::shut_down() {
 	memdelete(EditorVCSInterface::get_singleton());
 	EditorVCSInterface::set_singleton(nullptr);
 
-	EditorNode::get_singleton()->remove_control_from_dock(version_commit_dock);
+	EditorDockManager::get_singleton()->remove_control_from_dock(version_commit_dock);
 	EditorNode::get_singleton()->remove_bottom_panel_item(version_control_dock);
 
 	_set_vcs_ui_state(false);

+ 9 - 9
scene/gui/split_container.cpp

@@ -39,7 +39,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 
 	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 
-	if (sc->collapsed || !sc->_getch(0) || !sc->_getch(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
+	if (sc->collapsed || !sc->get_containable_child(0) || !sc->get_containable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
 		return;
 	}
 
@@ -122,7 +122,7 @@ void SplitContainerDragger::_notification(int p_what) {
 	}
 }
 
-Control *SplitContainer::_getch(int p_idx) const {
+Control *SplitContainer::get_containable_child(int p_idx) const {
 	int idx = 0;
 
 	for (int i = 0; i < get_child_count(false); i++) {
@@ -157,8 +157,8 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const {
 }
 
 void SplitContainer::_compute_middle_sep(bool p_clamp) {
-	Control *first = _getch(0);
-	Control *second = _getch(1);
+	Control *first = get_containable_child(0);
+	Control *second = get_containable_child(1);
 
 	// Determine expanded children.
 	bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND;
@@ -199,8 +199,8 @@ void SplitContainer::_compute_middle_sep(bool p_clamp) {
 }
 
 void SplitContainer::_resort() {
-	Control *first = _getch(0);
-	Control *second = _getch(1);
+	Control *first = get_containable_child(0);
+	Control *second = get_containable_child(1);
 
 	// If we have only one element.
 	if (!first || !second) {
@@ -261,7 +261,7 @@ Size2 SplitContainer::get_minimum_size() const {
 	int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
 
 	for (int i = 0; i < 2; i++) {
-		if (!_getch(i)) {
+		if (!get_containable_child(i)) {
 			break;
 		}
 
@@ -273,7 +273,7 @@ Size2 SplitContainer::get_minimum_size() const {
 			}
 		}
 
-		Size2 ms = _getch(i)->get_combined_minimum_size();
+		Size2 ms = get_containable_child(i)->get_combined_minimum_size();
 
 		if (vertical) {
 			minimum.height += ms.height;
@@ -325,7 +325,7 @@ int SplitContainer::get_split_offset() const {
 }
 
 void SplitContainer::clamp_split_offset() {
-	if (!_getch(0) || !_getch(1)) {
+	if (!get_containable_child(0) || !get_containable_child(1)) {
 		return;
 	}
 

+ 2 - 2
scene/gui/split_container.h

@@ -79,8 +79,6 @@ private:
 		Ref<Texture2D> grabber_icon_v;
 	} theme_cache;
 
-	Control *_getch(int p_idx) const;
-
 	Ref<Texture2D> _get_grabber_icon() const;
 	void _compute_middle_sep(bool p_clamp);
 	void _resort();
@@ -88,6 +86,8 @@ private:
 protected:
 	bool is_fixed = false;
 
+	Control *get_containable_child(int p_idx) const;
+
 	void _notification(int p_what);
 	void _validate_property(PropertyInfo &p_property) const;
 	static void _bind_methods();