Path: blob/master/editor/docks/editor_dock_manager.cpp
9896 views
/**************************************************************************/1/* editor_dock_manager.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_dock_manager.h"3132#include "scene/gui/box_container.h"33#include "scene/gui/button.h"34#include "scene/gui/label.h"35#include "scene/gui/split_container.h"36#include "scene/gui/tab_container.h"37#include "scene/main/window.h"3839#include "editor/editor_node.h"40#include "editor/editor_string_names.h"41#include "editor/gui/editor_bottom_panel.h"42#include "editor/gui/window_wrapper.h"43#include "editor/settings/editor_settings.h"44#include "editor/themes/editor_scale.h"45#include "scene/resources/style_box_flat.h"4647enum class TabStyle {48TEXT_ONLY,49ICON_ONLY,50TEXT_AND_ICON,51};5253EditorDockManager *EditorDockManager::singleton = nullptr;5455bool EditorDockDragHint::can_drop_data(const Point2 &p_point, const Variant &p_data) const {56return can_drop_dock;57}5859void EditorDockDragHint::drop_data(const Point2 &p_point, const Variant &p_data) {60// Drop dock into last spot if not over tabbar.61if (drop_tabbar->get_rect().has_point(p_point)) {62drop_tabbar->_handle_drop_data("tab_container_tab", p_point, p_data, callable_mp(this, &EditorDockDragHint::_drag_move_tab), callable_mp(this, &EditorDockDragHint::_drag_move_tab_from));63} else {64dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], drop_tabbar->get_tab_count());65}66}6768void EditorDockDragHint::_drag_move_tab(int p_from_index, int p_to_index) {69dock_manager->_move_dock_tab_index(dock_manager->_get_dock_tab_dragged(), p_to_index, true);70}7172void EditorDockDragHint::_drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {73dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], p_to_index);74}7576void EditorDockDragHint::gui_input(const Ref<InputEvent> &p_event) {77ERR_FAIL_COND(p_event.is_null());7879Ref<InputEventMouseMotion> mm = p_event;80if (mm.is_valid()) {81Point2 pos = mm->get_position();8283// Redraw when inside the tabbar and just exited.84if (mouse_inside_tabbar) {85queue_redraw();86}87mouse_inside_tabbar = drop_tabbar->get_rect().has_point(pos);88}89}9091void EditorDockDragHint::set_slot(EditorDockManager::DockSlot p_slot) {92occupied_slot = p_slot;93drop_tabbar = dock_manager->dock_slot[occupied_slot]->get_tab_bar();94}9596void EditorDockDragHint::_notification(int p_what) {97switch (p_what) {98case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {99if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme")) {100dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int());101if (mouse_inside) {102queue_redraw();103}104}105} break;106107case NOTIFICATION_THEME_CHANGED: {108valid_drop_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));109} break;110111case NOTIFICATION_MOUSE_ENTER:112case NOTIFICATION_MOUSE_EXIT: {113mouse_inside = p_what == NOTIFICATION_MOUSE_ENTER;114queue_redraw();115} break;116117case NOTIFICATION_DRAG_BEGIN: {118Control *dragged_dock = dock_manager->_get_dock_tab_dragged();119if (!dragged_dock) {120return;121}122123can_drop_dock = true;124125dock_drop_highlight->set_border_color(valid_drop_color);126dock_drop_highlight->set_bg_color(valid_drop_color * Color(1, 1, 1, 0.1));127} break;128case NOTIFICATION_DRAG_END: {129dock_manager->_dock_drag_stopped();130can_drop_dock = false;131mouse_inside = false;132hide();133} break;134135case NOTIFICATION_DRAW: {136if (!mouse_inside) {137return;138}139140// Draw highlights around docks that can be dropped.141Rect2 dock_rect = Rect2(Point2(), get_size()).grow(2 * EDSCALE);142draw_style_box(dock_drop_highlight, dock_rect);143144// Only display tabbar hint if the mouse is over the tabbar.145if (drop_tabbar->get_global_rect().has_point(get_global_mouse_position())) {146drop_tabbar->_draw_tab_drop(get_canvas_item());147}148} break;149}150}151152EditorDockDragHint::EditorDockDragHint() {153dock_manager = EditorDockManager::get_singleton();154155set_as_top_level(true);156dock_drop_highlight.instantiate();157dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int());158dock_drop_highlight->set_border_width_all(Math::round(2 * EDSCALE));159}160161////////////////////////////////////////////////162////////////////////////////////////////////////163164void DockSplitContainer::_update_visibility() {165if (is_updating) {166return;167}168is_updating = true;169bool any_visible = false;170for (int i = 0; i < get_child_count(false); i++) {171Control *c = Object::cast_to<Control>(get_child(i, false));172if (!c || !c->is_visible() || c->is_set_as_top_level()) {173continue;174}175any_visible = c;176break;177}178set_visible(any_visible);179is_updating = false;180}181182void DockSplitContainer::add_child_notify(Node *p_child) {183SplitContainer::add_child_notify(p_child);184185Control *child_control = nullptr;186for (int i = 0; i < get_child_count(false); i++) {187Control *c = Object::cast_to<Control>(get_child(i, false));188if (!c || c->is_set_as_top_level()) {189continue;190}191if (p_child == c) {192child_control = c;193break;194}195}196if (!child_control) {197return;198}199200child_control->connect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));201_update_visibility();202}203204void DockSplitContainer::remove_child_notify(Node *p_child) {205SplitContainer::remove_child_notify(p_child);206207Control *child_control = nullptr;208for (int i = 0; i < get_child_count(false); i++) {209Control *c = Object::cast_to<Control>(get_child(i, false));210if (!c || c->is_set_as_top_level()) {211continue;212}213if (p_child == c) {214child_control = c;215break;216}217}218if (!child_control) {219return;220}221222child_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));223_update_visibility();224}225226DockSplitContainer::DockSplitContainer() {227if (EDITOR_GET("interface/touchscreen/enable_touch_optimizations")) {228callable_mp((SplitContainer *)this, &SplitContainer::set_touch_dragger_enabled).call_deferred(true);229}230}231232////////////////////////////////////////////////233////////////////////////////////////////////////234235Control *EditorDockManager::_get_dock_tab_dragged() {236if (dock_tab_dragged) {237return dock_tab_dragged;238}239240Dictionary dock_drop_data = dock_slot[DOCK_SLOT_LEFT_BL]->get_viewport()->gui_get_drag_data();241242// Check if we are dragging a dock.243const String type = dock_drop_data.get("type", "");244if (type == "tab_container_tab") {245Node *from_node = dock_slot[DOCK_SLOT_LEFT_BL]->get_node(dock_drop_data["from_path"]);246if (!from_node) {247return nullptr;248}249250TabContainer *parent = Object::cast_to<TabContainer>(from_node->get_parent());251if (!parent) {252return nullptr;253}254255// TODO: Update logic when GH-106503 is merged to cast directly to EditorDock instead of the below check.256for (int i = 0; i < DOCK_SLOT_MAX; i++) {257if (dock_slot[i] == parent) {258dock_tab_dragged = parent->get_tab_control(dock_drop_data["tab_index"]);259break;260}261}262if (!dock_tab_dragged) {263return nullptr;264}265266for (int i = 0; i < DOCK_SLOT_MAX; i++) {267if (dock_slot[i]->is_visible_in_tree()) {268dock_drag_rects[i]->set_rect(dock_slot[i]->get_global_rect());269dock_drag_rects[i]->show();270}271}272273return dock_tab_dragged;274}275return nullptr;276}277278void EditorDockManager::_dock_drag_stopped() {279dock_tab_dragged = nullptr;280}281282void EditorDockManager::_dock_split_dragged(int p_offset) {283EditorNode::get_singleton()->save_editor_layout_delayed();284}285286void EditorDockManager::_dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container) {287Ref<InputEventMouseButton> mb = p_input;288289if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {290int tab_id = p_dock_container->get_tab_bar()->get_hovered_tab();291if (tab_id < 0) {292return;293}294295// Right click context menu.296dock_context_popup->set_dock(p_dock_container->get_tab_control(tab_id));297dock_context_popup->set_position(p_dock_container->get_screen_position() + mb->get_position());298dock_context_popup->popup();299}300}301302void EditorDockManager::_bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, Control *p_dock, Button *p_bottom_button) {303Ref<InputEventMouseButton> mb = p_input;304305if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {306// Right click context menu.307dock_context_popup->set_dock(p_dock);308dock_context_popup->set_position(p_bottom_button->get_screen_position() + mb->get_position());309dock_context_popup->popup();310}311}312313void EditorDockManager::_dock_container_update_visibility(TabContainer *p_dock_container) {314if (!docks_visible) {315return;316}317// Hide the dock container if there are no tabs.318p_dock_container->set_visible(p_dock_container->get_tab_count() > 0);319}320321void EditorDockManager::_update_layout() {322if (!dock_context_popup->is_inside_tree() || EditorNode::get_singleton()->is_exiting()) {323return;324}325dock_context_popup->docks_updated();326update_docks_menu();327EditorNode::get_singleton()->save_editor_layout_delayed();328}329330void EditorDockManager::update_docks_menu() {331docks_menu->clear();332docks_menu->reset_size();333334const Ref<Texture2D> default_icon = docks_menu->get_editor_theme_icon(SNAME("Window"));335const Color closed_icon_color_mod = Color(1, 1, 1, 0.5);336337bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);338bool dark_mode = DisplayServer::get_singleton()->is_dark_mode_supported() && DisplayServer::get_singleton()->is_dark_mode();339340// Add docks.341docks_menu_docks.clear();342int id = 0;343for (const KeyValue<Control *, DockInfo> &dock : all_docks) {344if (!dock.value.enabled) {345continue;346}347if (dock.value.shortcut.is_valid()) {348docks_menu->add_shortcut(dock.value.shortcut, id);349docks_menu->set_item_text(id, dock.value.title);350} else {351docks_menu->add_item(dock.value.title, id);352}353const Ref<Texture2D> icon = dock.value.icon_name ? docks_menu->get_editor_theme_native_menu_icon(dock.value.icon_name, global_menu, dark_mode) : dock.value.icon;354docks_menu->set_item_icon(id, icon.is_valid() ? icon : default_icon);355if (!dock.value.open) {356docks_menu->set_item_icon_modulate(id, closed_icon_color_mod);357docks_menu->set_item_tooltip(id, vformat(TTR("Open the %s dock."), dock.value.title));358} else {359docks_menu->set_item_tooltip(id, vformat(TTR("Focus on the %s dock."), dock.value.title));360}361docks_menu_docks.push_back(dock.key);362id++;363}364}365366void EditorDockManager::_docks_menu_option(int p_id) {367Control *dock = docks_menu_docks[p_id];368ERR_FAIL_NULL(dock);369ERR_FAIL_COND_MSG(!all_docks.has(dock), vformat("Menu option for unknown dock '%s'.", dock->get_name()));370if (all_docks[dock].enabled && all_docks[dock].open) {371PopupMenu *parent_menu = Object::cast_to<PopupMenu>(docks_menu->get_parent());372ERR_FAIL_NULL(parent_menu);373parent_menu->hide();374}375focus_dock(dock);376}377378void EditorDockManager::_window_close_request(WindowWrapper *p_wrapper) {379// Give the dock back to the original owner.380Control *dock = _close_window(p_wrapper);381ERR_FAIL_COND(!all_docks.has(dock));382383if (all_docks[dock].previous_at_bottom || all_docks[dock].dock_slot_index != DOCK_SLOT_NONE) {384all_docks[dock].open = false;385open_dock(dock);386focus_dock(dock);387} else {388close_dock(dock);389}390}391392Control *EditorDockManager::_close_window(WindowWrapper *p_wrapper) {393p_wrapper->set_block_signals(true);394Control *dock = p_wrapper->release_wrapped_control();395p_wrapper->set_block_signals(false);396ERR_FAIL_COND_V(!all_docks.has(dock), nullptr);397398all_docks[dock].dock_window = nullptr;399dock_windows.erase(p_wrapper);400p_wrapper->queue_free();401return dock;402}403404void EditorDockManager::_open_dock_in_window(Control *p_dock, bool p_show_window, bool p_reset_size) {405ERR_FAIL_NULL(p_dock);406407Size2 borders = Size2(4, 4) * EDSCALE;408// Remember size and position before removing it from the main window.409Size2 dock_size = p_dock->get_size() + borders * 2;410Point2 dock_screen_pos = p_dock->get_screen_position();411412WindowWrapper *wrapper = memnew(WindowWrapper);413wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), all_docks[p_dock].title));414wrapper->set_margins_enabled(true);415416EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);417418_move_dock(p_dock, nullptr);419wrapper->set_wrapped_control(p_dock);420421all_docks[p_dock].dock_window = wrapper;422all_docks[p_dock].open = true;423p_dock->show();424425wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_window_close_request).bind(wrapper));426dock_windows.push_back(wrapper);427428if (p_show_window) {429wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());430_update_layout();431if (p_reset_size) {432// Use a default size of one third the current window size.433Size2i popup_size = EditorNode::get_singleton()->get_window()->get_size() / 3.0;434p_dock->get_window()->set_size(popup_size);435p_dock->get_window()->move_to_center();436}437p_dock->get_window()->grab_focus();438}439}440441void EditorDockManager::_restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump) {442if (!all_docks[p_dock].dock_window) {443_open_dock_in_window(p_dock, false);444}445446all_docks[p_dock].dock_window->restore_window_from_saved_position(447p_window_dump.get("window_rect", Rect2i()),448p_window_dump.get("window_screen", -1),449p_window_dump.get("window_screen_rect", Rect2i()));450}451452void EditorDockManager::_dock_move_to_bottom(Control *p_dock, bool p_visible) {453_move_dock(p_dock, nullptr);454455all_docks[p_dock].at_bottom = true;456all_docks[p_dock].previous_at_bottom = false;457458p_dock->call("_set_dock_horizontal", true);459460// Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.461Button *bottom_button = EditorNode::get_bottom_panel()->add_item(all_docks[p_dock].title, p_dock, all_docks[p_dock].shortcut, true);462bottom_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_bottom_dock_button_gui_input).bind(bottom_button).bind(p_dock));463EditorNode::get_bottom_panel()->make_item_visible(p_dock, p_visible);464}465466void EditorDockManager::_dock_remove_from_bottom(Control *p_dock) {467all_docks[p_dock].at_bottom = false;468all_docks[p_dock].previous_at_bottom = true;469470EditorNode::get_bottom_panel()->remove_item(p_dock);471p_dock->call("_set_dock_horizontal", false);472}473474bool EditorDockManager::_is_dock_at_bottom(Control *p_dock) {475ERR_FAIL_COND_V(!all_docks.has(p_dock), false);476return all_docks[p_dock].at_bottom;477}478479void EditorDockManager::_move_dock_tab_index(Control *p_dock, int p_tab_index, bool p_set_current) {480TabContainer *dock_tab_container = Object::cast_to<TabContainer>(p_dock->get_parent());481if (!dock_tab_container) {482return;483}484485dock_tab_container->set_block_signals(true);486int target_index = CLAMP(p_tab_index, 0, dock_tab_container->get_tab_count() - 1);487dock_tab_container->move_child(p_dock, dock_tab_container->get_tab_control(target_index)->get_index(false));488all_docks[p_dock].previous_tab_index = target_index;489490if (p_set_current) {491dock_tab_container->set_current_tab(target_index);492}493dock_tab_container->set_block_signals(false);494}495496void EditorDockManager::_move_dock(Control *p_dock, Control *p_target, int p_tab_index, bool p_set_current) {497ERR_FAIL_NULL(p_dock);498ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot move unknown dock '%s'.", p_dock->get_name()));499500Node *parent = p_dock->get_parent();501if (parent == p_target) {502if (p_tab_index >= 0 && parent) {503// Only change the tab index.504_move_dock_tab_index(p_dock, p_tab_index, p_set_current);505}506return;507}508509// Remove dock from its existing parent.510if (parent) {511if (all_docks[p_dock].dock_window) {512_close_window(all_docks[p_dock].dock_window);513} else if (all_docks[p_dock].at_bottom) {514_dock_remove_from_bottom(p_dock);515} else {516all_docks[p_dock].previous_at_bottom = false;517TabContainer *parent_tabs = Object::cast_to<TabContainer>(parent);518if (parent_tabs) {519all_docks[p_dock].previous_tab_index = parent_tabs->get_tab_idx_from_control(p_dock);520}521parent->set_block_signals(true);522parent->remove_child(p_dock);523parent->set_block_signals(false);524if (parent_tabs) {525_dock_container_update_visibility(parent_tabs);526}527}528}529530// Add dock to its new parent, at the given tab index.531if (!p_target) {532return;533}534p_target->set_block_signals(true);535p_target->add_child(p_dock);536p_target->set_block_signals(false);537TabContainer *dock_tab_container = Object::cast_to<TabContainer>(p_target);538if (dock_tab_container) {539if (dock_tab_container->is_inside_tree()) {540_update_tab_style(p_dock);541}542if (p_tab_index >= 0) {543_move_dock_tab_index(p_dock, p_tab_index, p_set_current);544}545_dock_container_update_visibility(dock_tab_container);546}547}548549void EditorDockManager::_update_tab_style(Control *p_dock) {550const DockInfo &dock_info = all_docks[p_dock];551if (!dock_info.enabled || !dock_info.open) {552return; // Disabled by feature profile or manually closed by user.553}554if (dock_info.dock_window || dock_info.at_bottom) {555return; // Floating or sent to bottom.556}557558TabContainer *tab_container = get_dock_tab_container(p_dock);559ERR_FAIL_NULL(tab_container);560int index = tab_container->get_tab_idx_from_control(p_dock);561ERR_FAIL_COND(index == -1);562563const TabStyle style = (TabStyle)EDITOR_GET("interface/editor/dock_tab_style").operator int();564switch (style) {565case TabStyle::TEXT_ONLY: {566tab_container->set_tab_title(index, dock_info.title);567tab_container->set_tab_icon(index, Ref<Texture2D>());568tab_container->set_tab_tooltip(index, String());569} break;570case TabStyle::ICON_ONLY: {571const Ref<Texture2D> icon = dock_info.icon_name ? tab_container->get_editor_theme_icon(dock_info.icon_name) : dock_info.icon;572tab_container->set_tab_title(index, icon.is_valid() ? String() : dock_info.title);573tab_container->set_tab_icon(index, icon);574tab_container->set_tab_tooltip(index, icon.is_valid() ? dock_info.title : String());575} break;576case TabStyle::TEXT_AND_ICON: {577const Ref<Texture2D> icon = dock_info.icon_name ? tab_container->get_editor_theme_icon(dock_info.icon_name) : dock_info.icon;578tab_container->set_tab_title(index, dock_info.title);579tab_container->set_tab_icon(index, icon);580tab_container->set_tab_tooltip(index, String());581} break;582}583}584585void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {586// Save docks by dock slot.587for (int i = 0; i < DOCK_SLOT_MAX; i++) {588String names;589for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {590String name = dock_slot[i]->get_tab_control(j)->get_name();591if (!names.is_empty()) {592names += ",";593}594names += name;595}596597String config_key = "dock_" + itos(i + 1);598599if (p_layout->has_section_key(p_section, config_key)) {600p_layout->erase_section_key(p_section, config_key);601}602603if (!names.is_empty()) {604p_layout->set_value(p_section, config_key, names);605}606607int selected_tab_idx = dock_slot[i]->get_current_tab();608if (selected_tab_idx >= 0) {609p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx);610}611}612if (p_layout->has_section_key(p_section, "dock_0")) {613// Clear the keys where the dock has no slot so it is overridden.614p_layout->erase_section_key(p_section, "dock_0");615}616617// Save docks in windows.618Dictionary floating_docks_dump;619for (WindowWrapper *wrapper : dock_windows) {620Control *dock = wrapper->get_wrapped_control();621622Dictionary window_dump;623window_dump["window_rect"] = wrapper->get_window_rect();624625int screen = wrapper->get_window_screen();626window_dump["window_screen"] = wrapper->get_window_screen();627window_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);628629String name = dock->get_name();630floating_docks_dump[name] = window_dump;631632// Append to regular dock section so we know where to restore it to.633int dock_slot_id = all_docks[dock].dock_slot_index;634String config_key = "dock_" + itos(dock_slot_id + 1);635636String names = p_layout->get_value(p_section, config_key, "");637if (names.is_empty()) {638names = name;639} else {640names += "," + name;641}642p_layout->set_value(p_section, config_key, names);643}644p_layout->set_value(p_section, "dock_floating", floating_docks_dump);645646// Save closed and bottom docks.647Array bottom_docks_dump;648Array closed_docks_dump;649for (const KeyValue<Control *, DockInfo> &d : all_docks) {650d.key->call(SNAME("_save_layout_to_config"), p_layout, p_section);651652if (!d.value.at_bottom && d.value.open && (!d.value.previous_at_bottom || !d.value.dock_window)) {653continue;654}655// Use the name of the Control since it isn't translated.656String name = d.key->get_name();657if (d.value.at_bottom || (d.value.previous_at_bottom && d.value.dock_window)) {658bottom_docks_dump.push_back(name);659}660if (!d.value.open) {661closed_docks_dump.push_back(name);662}663664int dock_slot_id = all_docks[d.key].dock_slot_index;665String config_key = "dock_" + itos(dock_slot_id + 1);666667String names = p_layout->get_value(p_section, config_key, "");668if (names.is_empty()) {669names = name;670} else {671names += "," + name;672}673p_layout->set_value(p_section, config_key, names);674}675p_layout->set_value(p_section, "dock_bottom", bottom_docks_dump);676p_layout->set_value(p_section, "dock_closed", closed_docks_dump);677678// Save SplitContainer offsets.679for (int i = 0; i < vsplits.size(); i++) {680if (vsplits[i]->is_visible_in_tree()) {681p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());682}683}684685for (int i = 0; i < hsplits.size(); i++) {686p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE));687}688}689690void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section, bool p_first_load) {691Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());692Array dock_bottom = p_layout->get_value(p_section, "dock_bottom", Array());693Array closed_docks = p_layout->get_value(p_section, "dock_closed", Array());694695bool allow_floating_docks = EditorNode::get_singleton()->is_multi_window_enabled() && (!p_first_load || EDITOR_GET("interface/multi_window/restore_windows_on_load"));696697// Store the docks by name for easy lookup.698HashMap<String, Control *> dock_map;699for (const KeyValue<Control *, DockInfo> &dock : all_docks) {700dock_map[dock.key->get_name()] = dock.key;701}702703// Load docks by slot. Index -1 is for docks that have no slot.704for (int i = -1; i < DOCK_SLOT_MAX; i++) {705if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) {706continue;707}708709Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(",");710711for (int j = names.size() - 1; j >= 0; j--) {712String name = names[j];713714if (!dock_map.has(name)) {715continue;716}717Control *dock = dock_map[name];718719if (!all_docks[dock].enabled) {720// Don't open disabled docks.721dock->call(SNAME("_load_layout_from_config"), p_layout, p_section);722continue;723}724bool at_bottom = false;725if (allow_floating_docks && floating_docks_dump.has(name)) {726all_docks[dock].previous_at_bottom = dock_bottom.has(name);727_restore_dock_to_saved_window(dock, floating_docks_dump[name]);728} else if (dock_bottom.has(name)) {729_dock_move_to_bottom(dock, false);730at_bottom = true;731} else if (i >= 0) {732_move_dock(dock, dock_slot[i], 0);733}734dock->call(SNAME("_load_layout_from_config"), p_layout, p_section);735736if (closed_docks.has(name)) {737_move_dock(dock, closed_dock_parent);738all_docks[dock].open = false;739dock->hide();740} else {741// Make sure it is open.742all_docks[dock].open = true;743// It's important to not update the visibility of bottom panels.744// Visibility of bottom panels are managed in EditorBottomPanel.745if (!at_bottom) {746dock->show();747}748}749750all_docks[dock].dock_slot_index = i;751all_docks[dock].previous_tab_index = i >= 0 ? j : 0;752}753}754755// Set the selected tabs.756for (int i = 0; i < DOCK_SLOT_MAX; i++) {757if (dock_slot[i]->get_tab_count() == 0 || !p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {758continue;759}760int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx");761if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) {762dock_slot[i]->set_block_signals(true);763dock_slot[i]->set_current_tab(selected_tab_idx);764dock_slot[i]->set_block_signals(false);765}766}767768// Load SplitContainer offsets.769for (int i = 0; i < vsplits.size(); i++) {770if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {771continue;772}773int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));774vsplits[i]->set_split_offset(ofs);775}776777for (int i = 0; i < hsplits.size(); i++) {778if (!p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {779continue;780}781int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));782hsplits[i]->set_split_offset(ofs * EDSCALE);783}784update_docks_menu();785}786787void EditorDockManager::bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock) {788ERR_FAIL_COND(!all_docks.has(p_dock));789790dock_context_popup->set_dock(p_dock);791792Vector2 popup_pos = p_position.position;793popup_pos.y += p_position.size.height;794795if (!EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {796popup_pos.x -= dock_context_popup->get_size().width;797popup_pos.x += p_position.size.width;798}799dock_context_popup->set_position(popup_pos);800dock_context_popup->popup();801}802803void EditorDockManager::set_dock_enabled(Control *p_dock, bool p_enabled) {804ERR_FAIL_NULL(p_dock);805ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set enabled unknown dock '%s'.", p_dock->get_name()));806807if (all_docks[p_dock].enabled == p_enabled) {808return;809}810811all_docks[p_dock].enabled = p_enabled;812if (p_enabled) {813open_dock(p_dock, false);814} else {815close_dock(p_dock);816}817}818819void EditorDockManager::close_dock(Control *p_dock) {820ERR_FAIL_NULL(p_dock);821ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot close unknown dock '%s'.", p_dock->get_name()));822823if (!all_docks[p_dock].open) {824return;825}826827_move_dock(p_dock, closed_dock_parent);828829all_docks[p_dock].open = false;830p_dock->hide();831832_update_layout();833}834835void EditorDockManager::open_dock(Control *p_dock, bool p_set_current) {836ERR_FAIL_NULL(p_dock);837ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot open unknown dock '%s'.", p_dock->get_name()));838839if (all_docks[p_dock].open) {840return;841}842843all_docks[p_dock].open = true;844p_dock->show();845846// Open dock to its previous location.847if (all_docks[p_dock].previous_at_bottom) {848_dock_move_to_bottom(p_dock, true);849} else if (all_docks[p_dock].dock_slot_index != DOCK_SLOT_NONE) {850TabContainer *slot = dock_slot[all_docks[p_dock].dock_slot_index];851int tab_index = all_docks[p_dock].previous_tab_index;852if (tab_index < 0) {853tab_index = slot->get_tab_count();854}855_move_dock(p_dock, slot, tab_index, p_set_current);856} else {857_open_dock_in_window(p_dock, true, true);858return;859}860861_update_layout();862}863864TabContainer *EditorDockManager::get_dock_tab_container(Control *p_dock) const {865return Object::cast_to<TabContainer>(p_dock->get_parent());866}867868void EditorDockManager::focus_dock(Control *p_dock) {869ERR_FAIL_NULL(p_dock);870ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot focus unknown dock '%s'.", p_dock->get_name()));871872if (!all_docks[p_dock].enabled) {873return;874}875876if (!all_docks[p_dock].open) {877open_dock(p_dock);878}879880if (all_docks[p_dock].dock_window) {881p_dock->get_window()->grab_focus();882return;883}884885if (all_docks[p_dock].at_bottom) {886EditorNode::get_bottom_panel()->make_item_visible(p_dock, true, true);887return;888}889890if (!docks_visible) {891return;892}893894TabContainer *tab_container = get_dock_tab_container(p_dock);895if (!tab_container) {896return;897}898int tab_index = tab_container->get_tab_idx_from_control(p_dock);899tab_container->get_tab_bar()->grab_focus();900tab_container->set_current_tab(tab_index);901}902903void EditorDockManager::add_dock(Control *p_dock, const String &p_title, DockSlot p_slot, const Ref<Shortcut> &p_shortcut, const StringName &p_icon_name) {904ERR_FAIL_NULL(p_dock);905ERR_FAIL_COND_MSG(all_docks.has(p_dock), vformat("Cannot add dock '%s', already added.", p_dock->get_name()));906907DockInfo dock_info;908dock_info.title = p_title.is_empty() ? String(p_dock->get_name()) : p_title;909dock_info.dock_slot_index = p_slot;910dock_info.shortcut = p_shortcut;911dock_info.icon_name = p_icon_name;912all_docks[p_dock] = dock_info;913914if (p_slot != DOCK_SLOT_NONE) {915ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);916open_dock(p_dock, false);917} else {918closed_dock_parent->add_child(p_dock);919p_dock->hide();920_update_layout();921}922}923924void EditorDockManager::remove_dock(Control *p_dock) {925ERR_FAIL_NULL(p_dock);926ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot remove unknown dock '%s'.", p_dock->get_name()));927928_move_dock(p_dock, nullptr);929930all_docks.erase(p_dock);931_update_layout();932}933934void EditorDockManager::set_dock_tab_icon(Control *p_dock, const Ref<Texture2D> &p_icon) {935ERR_FAIL_NULL(p_dock);936ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set tab icon for unknown dock '%s'.", p_dock->get_name()));937938all_docks[p_dock].icon = p_icon;939_update_tab_style(p_dock);940}941942void EditorDockManager::set_docks_visible(bool p_show) {943if (docks_visible == p_show) {944return;945}946docks_visible = p_show;947for (int i = 0; i < DOCK_SLOT_MAX; i++) {948dock_slot[i]->set_visible(docks_visible && dock_slot[i]->get_tab_count() > 0);949}950_update_layout();951}952953bool EditorDockManager::are_docks_visible() const {954return docks_visible;955}956957void EditorDockManager::update_tab_styles() {958for (const KeyValue<Control *, DockInfo> &dock : all_docks) {959_update_tab_style(dock.key);960}961}962963void EditorDockManager::set_tab_icon_max_width(int p_max_width) {964for (int i = 0; i < DOCK_SLOT_MAX; i++) {965TabContainer *tab_container = dock_slot[i];966tab_container->add_theme_constant_override(SNAME("icon_max_width"), p_max_width);967}968}969970void EditorDockManager::add_vsplit(DockSplitContainer *p_split) {971vsplits.push_back(p_split);972p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));973}974975void EditorDockManager::add_hsplit(DockSplitContainer *p_split) {976hsplits.push_back(p_split);977p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));978}979980void EditorDockManager::register_dock_slot(DockSlot p_dock_slot, TabContainer *p_tab_container) {981ERR_FAIL_NULL(p_tab_container);982ERR_FAIL_INDEX(p_dock_slot, DOCK_SLOT_MAX);983984dock_slot[p_dock_slot] = p_tab_container;985986p_tab_container->set_custom_minimum_size(Size2(170, 0) * EDSCALE);987p_tab_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);988p_tab_container->set_popup(dock_context_popup);989p_tab_container->connect("pre_popup_pressed", callable_mp(dock_context_popup, &DockContextPopup::select_current_dock_in_dock_slot).bind(p_dock_slot));990p_tab_container->set_drag_to_rearrange_enabled(true);991p_tab_container->set_tabs_rearrange_group(1);992p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));993p_tab_container->connect("active_tab_rearranged", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));994p_tab_container->connect("child_order_changed", callable_mp(this, &EditorDockManager::_dock_container_update_visibility).bind(p_tab_container));995p_tab_container->set_use_hidden_tabs_for_min_size(true);996p_tab_container->get_tab_bar()->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_dock_container_gui_input).bind(p_tab_container));997p_tab_container->hide();998999// Create dock dragging hint.1000dock_drag_rects[p_dock_slot] = memnew(EditorDockDragHint);1001dock_drag_rects[p_dock_slot]->set_slot(p_dock_slot);1002dock_drag_rects[p_dock_slot]->hide();1003EditorNode::get_singleton()->get_gui_base()->add_child(dock_drag_rects[p_dock_slot]);1004}10051006int EditorDockManager::get_vsplit_count() const {1007return vsplits.size();1008}10091010PopupMenu *EditorDockManager::get_docks_menu() {1011return docks_menu;1012}10131014EditorDockManager::EditorDockManager() {1015singleton = this;10161017closed_dock_parent = EditorNode::get_singleton()->get_gui_base();10181019dock_context_popup = memnew(DockContextPopup);1020EditorNode::get_singleton()->get_gui_base()->add_child(dock_context_popup);10211022docks_menu = memnew(PopupMenu);1023docks_menu->set_hide_on_item_selection(false);1024docks_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDockManager::_docks_menu_option));1025EditorNode::get_singleton()->get_gui_base()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorDockManager::update_docks_menu));1026}10271028////////////////////////////////////////////////1029////////////////////////////////////////////////10301031void DockContextPopup::_notification(int p_what) {1032switch (p_what) {1033case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:1034case NOTIFICATION_TRANSLATION_CHANGED:1035case NOTIFICATION_THEME_CHANGED: {1036if (make_float_button) {1037make_float_button->set_button_icon(get_editor_theme_icon(SNAME("MakeFloating")));1038}1039if (is_layout_rtl()) {1040tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));1041tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));1042tab_move_left_button->set_tooltip_text(TTR("Move this dock right one tab."));1043tab_move_right_button->set_tooltip_text(TTR("Move this dock left one tab."));1044} else {1045tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));1046tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));1047tab_move_left_button->set_tooltip_text(TTR("Move this dock left one tab."));1048tab_move_right_button->set_tooltip_text(TTR("Move this dock right one tab."));1049}1050dock_to_bottom_button->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignBottomWide")));1051close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));1052} break;1053}1054}10551056void DockContextPopup::_tab_move_left() {1057TabContainer *tab_container = dock_manager->get_dock_tab_container(context_dock);1058if (!tab_container) {1059return;1060}1061int new_index = tab_container->get_tab_idx_from_control(context_dock) - 1;1062dock_manager->_move_dock(context_dock, tab_container, new_index);1063dock_manager->_update_layout();1064dock_select->queue_redraw();1065}10661067void DockContextPopup::_tab_move_right() {1068TabContainer *tab_container = dock_manager->get_dock_tab_container(context_dock);1069if (!tab_container) {1070return;1071}1072int new_index = tab_container->get_tab_idx_from_control(context_dock) + 1;1073dock_manager->_move_dock(context_dock, tab_container, new_index);1074dock_manager->_update_layout();1075dock_select->queue_redraw();1076}10771078void DockContextPopup::_close_dock() {1079hide();1080dock_manager->close_dock(context_dock);1081}10821083void DockContextPopup::_float_dock() {1084hide();1085dock_manager->_open_dock_in_window(context_dock);1086}10871088void DockContextPopup::_move_dock_to_bottom() {1089hide();1090dock_manager->_dock_move_to_bottom(context_dock, true);1091dock_manager->_update_layout();1092}10931094void DockContextPopup::_dock_select_input(const Ref<InputEvent> &p_input) {1095Ref<InputEventMouse> me = p_input;10961097if (me.is_valid()) {1098Vector2 point = me->get_position();10991100int over_dock_slot = -1;1101for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {1102if (dock_select_rects[i].has_point(point)) {1103over_dock_slot = i;1104break;1105}1106}11071108if (over_dock_slot != dock_select_rect_over_idx) {1109dock_select->queue_redraw();1110dock_select_rect_over_idx = over_dock_slot;1111}11121113if (over_dock_slot == -1) {1114return;1115}11161117Ref<InputEventMouseButton> mb = me;1118TabContainer *target_tab_container = dock_manager->dock_slot[over_dock_slot];11191120if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {1121if (dock_manager->get_dock_tab_container(context_dock) != target_tab_container) {1122dock_manager->_move_dock(context_dock, target_tab_container, target_tab_container->get_tab_count());1123dock_manager->all_docks[context_dock].dock_slot_index = over_dock_slot;1124dock_manager->_update_layout();1125hide();1126}1127}1128}1129}11301131void DockContextPopup::_dock_select_mouse_exited() {1132dock_select_rect_over_idx = -1;1133dock_select->queue_redraw();1134}11351136void DockContextPopup::_dock_select_draw() {1137Color used_dock_color = Color(0.6, 0.6, 0.6, 0.8);1138Color hovered_dock_color = Color(0.8, 0.8, 0.8, 0.8);1139Color tab_selected_color = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));1140Color tab_unselected_color = used_dock_color;1141Color unused_dock_color = used_dock_color;1142unused_dock_color.a = 0.4;1143Color unusable_dock_color = unused_dock_color;1144unusable_dock_color.a = 0.1;11451146// Update sizes.1147Size2 dock_size = dock_select->get_size();1148dock_size.x /= 6.0;1149dock_size.y /= 2.0;11501151Size2 center_panel_size = dock_size * 2.0;1152Rect2 center_panel_rect(center_panel_size.x, 0, center_panel_size.x, center_panel_size.y);11531154if (dock_select->is_layout_rtl()) {1155dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(), dock_size);1156dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(0, dock_size.y), dock_size);1157dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x, 0), dock_size);1158dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BL] = Rect2(dock_size, dock_size);1159dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x * 4, 0), dock_size);1160dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BR] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);1161dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UL] = Rect2(Point2(dock_size.x * 5, 0), dock_size);1162dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BL] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);1163} else {1164dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UL] = Rect2(Point2(), dock_size);1165dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BL] = Rect2(Point2(0, dock_size.y), dock_size);1166dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x, 0), dock_size);1167dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BR] = Rect2(dock_size, dock_size);1168dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x * 4, 0), dock_size);1169dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BL] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);1170dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(dock_size.x * 5, 0), dock_size);1171dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);1172}11731174int max_tabs = 3;1175int rtl_dir = dock_select->is_layout_rtl() ? -1 : 1;1176real_t tab_height = 3.0 * EDSCALE;1177real_t tab_spacing = 1.0 * EDSCALE;1178real_t dock_spacing = 2.0 * EDSCALE;1179real_t dock_top_spacing = tab_height + dock_spacing;11801181TabContainer *context_tab_container = dock_manager->get_dock_tab_container(context_dock);1182int context_tab_index = -1;1183if (context_tab_container && context_tab_container->get_tab_count() > 0) {1184context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);1185}11861187// Draw center panel.1188Rect2 center_panel_draw_rect = center_panel_rect.grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);1189dock_select->draw_rect(center_panel_draw_rect, unusable_dock_color);11901191// Draw all dock slots.1192for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {1193Rect2 dock_slot_draw_rect = dock_select_rects[i].grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);1194real_t tab_width = Math::round(dock_slot_draw_rect.size.width / max_tabs);1195Rect2 tab_draw_rect = Rect2(dock_slot_draw_rect.position.x, dock_select_rects[i].position.y, tab_width - tab_spacing, tab_height);1196if (dock_select->is_layout_rtl()) {1197tab_draw_rect.position.x += dock_slot_draw_rect.size.x - tab_draw_rect.size.x;1198}1199bool is_context_dock = context_tab_container == dock_manager->dock_slot[i];1200int tabs_to_draw = MIN(max_tabs, dock_manager->dock_slot[i]->get_tab_count());12011202if (i == dock_select_rect_over_idx) {1203dock_select->draw_rect(dock_slot_draw_rect, hovered_dock_color);1204} else if (tabs_to_draw == 0) {1205dock_select->draw_rect(dock_slot_draw_rect, unused_dock_color);1206} else {1207dock_select->draw_rect(dock_slot_draw_rect, used_dock_color);1208}12091210// Draw tabs above each used dock slot.1211for (int j = 0; j < tabs_to_draw; j++) {1212Color tab_color = tab_unselected_color;1213if (is_context_dock && context_tab_index == j) {1214tab_color = tab_selected_color;1215}1216Rect2 tabj_draw_rect = tab_draw_rect;1217tabj_draw_rect.position.x += tab_width * j * rtl_dir;1218dock_select->draw_rect(tabj_draw_rect, tab_color);1219}1220}1221}12221223void DockContextPopup::_update_buttons() {1224TabContainer *context_tab_container = dock_manager->get_dock_tab_container(context_dock);1225bool dock_at_bottom = dock_manager->_is_dock_at_bottom(context_dock);12261227// Update tab move buttons.1228tab_move_left_button->set_disabled(true);1229tab_move_right_button->set_disabled(true);1230if (!dock_at_bottom && context_tab_container && context_tab_container->get_tab_count() > 0) {1231int context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);1232tab_move_left_button->set_disabled(context_tab_index == 0);1233tab_move_right_button->set_disabled(context_tab_index >= context_tab_container->get_tab_count() - 1);1234}12351236dock_to_bottom_button->set_visible(!dock_at_bottom && bool(context_dock->call("_can_dock_horizontal")));1237reset_size();1238}12391240void DockContextPopup::select_current_dock_in_dock_slot(int p_dock_slot) {1241context_dock = dock_manager->dock_slot[p_dock_slot]->get_current_tab_control();1242_update_buttons();1243}12441245void DockContextPopup::set_dock(Control *p_dock) {1246context_dock = p_dock;1247_update_buttons();1248}12491250Control *DockContextPopup::get_dock() const {1251return context_dock;1252}12531254void DockContextPopup::docks_updated() {1255if (!is_visible()) {1256return;1257}1258_update_buttons();1259}12601261DockContextPopup::DockContextPopup() {1262dock_manager = EditorDockManager::get_singleton();12631264dock_select_popup_vb = memnew(VBoxContainer);1265add_child(dock_select_popup_vb);12661267HBoxContainer *header_hb = memnew(HBoxContainer);1268tab_move_left_button = memnew(Button);1269tab_move_left_button->set_accessibility_name(TTRC("Move Tab Left"));1270tab_move_left_button->set_flat(true);1271tab_move_left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1272tab_move_left_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_left));1273header_hb->add_child(tab_move_left_button);12741275Label *position_label = memnew(Label);1276position_label->set_text(TTR("Dock Position"));1277position_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);1278position_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1279header_hb->add_child(position_label);12801281tab_move_right_button = memnew(Button);1282tab_move_right_button->set_accessibility_name(TTRC("Move Tab Right"));1283tab_move_right_button->set_flat(true);1284tab_move_right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1285tab_move_right_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_right));12861287header_hb->add_child(tab_move_right_button);1288dock_select_popup_vb->add_child(header_hb);12891290dock_select = memnew(Control);1291dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);1292dock_select->connect(SceneStringName(gui_input), callable_mp(this, &DockContextPopup::_dock_select_input));1293dock_select->connect(SceneStringName(draw), callable_mp(this, &DockContextPopup::_dock_select_draw));1294dock_select->connect(SceneStringName(mouse_exited), callable_mp(this, &DockContextPopup::_dock_select_mouse_exited));1295dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);1296dock_select_popup_vb->add_child(dock_select);12971298make_float_button = memnew(Button);1299make_float_button->set_text(TTR("Make Floating"));1300if (!EditorNode::get_singleton()->is_multi_window_enabled()) {1301make_float_button->set_disabled(true);1302make_float_button->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());1303} else {1304make_float_button->set_tooltip_text(TTR("Make this dock floating."));1305}1306make_float_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1307make_float_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);1308make_float_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_float_dock));1309dock_select_popup_vb->add_child(make_float_button);13101311dock_to_bottom_button = memnew(Button);1312dock_to_bottom_button->set_text(TTR("Move to Bottom"));1313dock_to_bottom_button->set_tooltip_text(TTR("Move this dock to the bottom panel."));1314dock_to_bottom_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1315dock_to_bottom_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);1316dock_to_bottom_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_move_dock_to_bottom));1317dock_to_bottom_button->hide();1318dock_select_popup_vb->add_child(dock_to_bottom_button);13191320close_button = memnew(Button);1321close_button->set_text(TTR("Close"));1322close_button->set_tooltip_text(TTR("Close this dock."));1323close_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1324close_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);1325close_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_close_dock));1326dock_select_popup_vb->add_child(close_button);1327}132813291330