Path: blob/master/editor/docks/editor_dock_manager.cpp
20898 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/docks/dock_tab_container.h"40#include "editor/docks/editor_dock.h"41#include "editor/editor_node.h"42#include "editor/editor_string_names.h"43#include "editor/gui/window_wrapper.h"44#include "editor/settings/editor_settings.h"45#include "editor/themes/editor_scale.h"4647////////////////////////////////////////////////48////////////////////////////////////////////////4950void DockSplitContainer::_update_visibility() {51if (is_updating) {52return;53}54is_updating = true;55bool any_visible = false;56for (int i = 0; i < get_child_count(false); i++) {57Control *c = Object::cast_to<Control>(get_child(i, false));58if (!c || !c->is_visible() || c->is_set_as_top_level()) {59continue;60}61any_visible = c;62break;63}64set_visible(any_visible);65is_updating = false;66}6768void DockSplitContainer::add_child_notify(Node *p_child) {69SplitContainer::add_child_notify(p_child);7071Control *child_control = nullptr;72for (int i = 0; i < get_child_count(false); i++) {73Control *c = Object::cast_to<Control>(get_child(i, false));74if (!c || c->is_set_as_top_level()) {75continue;76}77if (p_child == c) {78child_control = c;79break;80}81}82if (!child_control) {83return;84}8586child_control->connect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));87_update_visibility();88}8990void DockSplitContainer::remove_child_notify(Node *p_child) {91SplitContainer::remove_child_notify(p_child);9293Control *child_control = nullptr;94for (int i = 0; i < get_child_count(false); i++) {95Control *c = Object::cast_to<Control>(get_child(i, false));96if (!c || c->is_set_as_top_level()) {97continue;98}99if (p_child == c) {100child_control = c;101break;102}103}104if (!child_control) {105return;106}107108child_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));109_update_visibility();110}111112DockSplitContainer::DockSplitContainer() {113if (EDITOR_GET("interface/touchscreen/enable_touch_optimizations")) {114callable_mp((SplitContainer *)this, &SplitContainer::set_touch_dragger_enabled).call_deferred(true);115}116}117118////////////////////////////////////////////////119////////////////////////////////////////////////120121EditorDock *EditorDockManager::_get_dock_tab_dragged() {122if (dock_tab_dragged) {123return dock_tab_dragged;124}125126Dictionary dock_drop_data = EditorNode::get_singleton()->get_viewport()->gui_get_drag_data();127128// Check if we are dragging a dock.129if (dock_drop_data.get("type", "").operator String() != "tab") {130return nullptr;131}132133const String tab_type = dock_drop_data.get("tab_type", "");134if (tab_type == "tab_container_tab") {135Node *source_tab_bar = EditorNode::get_singleton()->get_node(dock_drop_data["from_path"]);136if (!source_tab_bar) {137return nullptr;138}139140DockTabContainer *source_tab_container = Object::cast_to<DockTabContainer>(source_tab_bar->get_parent());141if (!source_tab_container) {142return nullptr;143}144145dock_tab_dragged = source_tab_container->get_dock(dock_drop_data["tab_index"]);146if (!dock_tab_dragged) {147return nullptr;148}149150for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {151dock_slots[i]->show_drag_hint();152}153154return dock_tab_dragged;155}156return nullptr;157}158159void EditorDockManager::_dock_drag_stopped() {160dock_tab_dragged = nullptr;161}162163void EditorDockManager::_dock_split_dragged(int p_offset) {164EditorNode::get_singleton()->save_editor_layout_delayed();165}166167void EditorDockManager::_update_layout() {168if (!dock_context_popup->is_inside_tree() || EditorNode::get_singleton()->is_exiting()) {169return;170}171dock_context_popup->docks_updated();172update_docks_menu();173EditorNode::get_singleton()->save_editor_layout_delayed();174}175176void EditorDockManager::update_docks_menu() {177docks_menu->clear();178docks_menu->reset_size();179180const Ref<Texture2D> default_icon = docks_menu->get_editor_theme_icon(SNAME("Window"));181const Color closed_icon_color_mod = Color(1, 1, 1, 0.5);182183bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);184bool dark_mode = DisplayServer::get_singleton()->is_dark_mode_supported() && DisplayServer::get_singleton()->is_dark_mode();185int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));186187// Add docks.188docks_menu_docks.clear();189int id = 0;190const Callable icon_fetch = callable_mp(EditorNode::get_singleton(), &EditorNode::get_editor_theme_native_menu_icon).bind(global_menu, dark_mode);191for (EditorDock *dock : all_docks) {192if (!dock->enabled || !dock->global) {193continue;194}195if (dock->shortcut.is_valid()) {196docks_menu->add_shortcut(dock->shortcut, id);197docks_menu->set_item_text(id, dock->get_display_title());198} else {199docks_menu->add_item(dock->get_display_title(), id);200}201docks_menu->set_item_icon_max_width(id, icon_max_width);202203const Ref<Texture2D> icon = dock->get_effective_icon(icon_fetch);204docks_menu->set_item_icon(id, icon.is_valid() ? icon : default_icon);205if (!dock->is_open) {206docks_menu->set_item_icon_modulate(id, closed_icon_color_mod);207docks_menu->set_item_tooltip(id, vformat(TTR("Open the %s dock."), TTR(dock->get_display_title())));208} else {209docks_menu->set_item_tooltip(id, vformat(TTR("Focus on the %s dock."), TTR(dock->get_display_title())));210}211docks_menu_docks.push_back(dock);212id++;213}214}215216void EditorDockManager::_docks_menu_option(int p_id) {217EditorDock *dock = docks_menu_docks[p_id];218ERR_FAIL_NULL(dock);219ERR_FAIL_COND_MSG(!all_docks.has(dock), vformat("Menu option for unknown dock '%s'.", dock->get_display_title()));220if (dock->enabled && dock->is_open) {221PopupMenu *parent_menu = Object::cast_to<PopupMenu>(docks_menu->get_parent());222ERR_FAIL_NULL(parent_menu);223parent_menu->hide();224}225focus_dock(dock);226}227228void EditorDockManager::_window_close_request(WindowWrapper *p_wrapper) {229// Give the dock back to the original owner.230EditorDock *dock = _close_window(p_wrapper);231ERR_FAIL_COND(!all_docks.has(dock));232233if (dock->dock_slot_index != EditorDock::DOCK_SLOT_NONE) {234dock->is_open = false;235focus_dock(dock);236} else {237close_dock(dock);238}239}240241EditorDock *EditorDockManager::_close_window(WindowWrapper *p_wrapper) {242p_wrapper->set_block_signals(true);243EditorDock *dock = Object::cast_to<EditorDock>(p_wrapper->release_wrapped_control());244p_wrapper->set_block_signals(false);245ERR_FAIL_COND_V(!all_docks.has(dock), nullptr);246247dock->dock_window = nullptr;248dock_windows.erase(p_wrapper);249p_wrapper->queue_free();250return dock;251}252253void EditorDockManager::_open_dock_in_window(EditorDock *p_dock, bool p_show_window, bool p_reset_size) {254ERR_FAIL_NULL(p_dock);255256Size2 borders = Size2(4, 4) * EDSCALE;257// Remember size and position before removing it from the main window.258Size2 dock_size = p_dock->get_size() + borders * 2;259Point2 dock_screen_pos = p_dock->get_screen_position();260261WindowWrapper *wrapper = memnew(WindowWrapper);262wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR(p_dock->get_display_title())));263wrapper->set_margins_enabled(true);264265EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);266267_move_dock(p_dock, nullptr);268p_dock->update_layout(EditorDock::DOCK_LAYOUT_FLOATING);269p_dock->current_layout = EditorDock::DOCK_LAYOUT_FLOATING;270wrapper->set_wrapped_control(p_dock);271272p_dock->dock_window = wrapper;273p_dock->is_open = true;274p_dock->show();275276wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_window_close_request).bind(wrapper));277dock_windows.push_back(wrapper);278279if (p_show_window) {280wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());281_update_layout();282if (p_reset_size) {283// Use a default size of one third the current window size.284Size2i popup_size = EditorNode::get_singleton()->get_window()->get_size() / 3.0;285p_dock->get_window()->set_size(popup_size);286p_dock->get_window()->move_to_center();287}288p_dock->get_window()->grab_focus();289}290}291292void EditorDockManager::_restore_dock_to_saved_window(EditorDock *p_dock, const Dictionary &p_window_dump) {293if (!p_dock->dock_window) {294_open_dock_in_window(p_dock, false);295}296297p_dock->dock_window->restore_window_from_saved_position(298p_window_dump.get("window_rect", Rect2i()),299p_window_dump.get("window_screen", -1),300p_window_dump.get("window_screen_rect", Rect2i()));301}302303void EditorDockManager::_move_dock(EditorDock *p_dock, Control *p_target, int p_tab_index, bool p_set_current) {304ERR_FAIL_NULL(p_dock);305ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot move unknown dock '%s'.", p_dock->get_display_title()));306307Node *parent = p_dock->get_parent();308if (parent == p_target) {309if (parent && p_tab_index >= 0) {310// Only change the tab index.311p_dock->set_tab_index(p_tab_index, p_set_current);312}313return;314}315316// Remove dock from its existing parent.317if (parent) {318if (p_dock->dock_window) {319_close_window(p_dock->dock_window);320} else {321DockTabContainer *parent_tabs = Object::cast_to<DockTabContainer>(parent);322if (parent_tabs) {323p_dock->previous_tab_index = parent_tabs->get_tab_idx_from_control(p_dock);324}325parent->set_block_signals(true);326parent->remove_child(p_dock);327parent->set_block_signals(false);328if (parent_tabs) {329parent_tabs->update_visibility();330}331}332}333334if (!p_target) {335p_dock->is_open = false;336return;337}338339DockTabContainer *dock_tab_container = Object::cast_to<DockTabContainer>(p_target);340if (p_target != closed_dock_parent) {341if (dock_tab_container->layout != p_dock->current_layout) {342p_dock->update_layout(dock_tab_container->layout);343p_dock->current_layout = dock_tab_container->layout;344}345p_dock->dock_slot_index = dock_tab_container->dock_slot;346}347348// Add dock to its new parent, at the given tab index.349p_target->set_block_signals(true);350p_target->add_child(p_dock);351p_target->set_block_signals(false);352353if (dock_tab_container) {354if (dock_tab_container->is_inside_tree()) {355p_dock->update_tab_style();356}357if (p_tab_index >= 0) {358p_dock->set_tab_index(p_tab_index, p_set_current);359}360dock_tab_container->update_visibility();361}362}363364void EditorDockManager::_queue_update_tab_style(EditorDock *p_dock) {365if (dirty_docks.is_empty()) {366callable_mp(this, &EditorDockManager::_update_dirty_dock_tabs).call_deferred();367}368dirty_docks.insert(p_dock);369}370371void EditorDockManager::_update_dirty_dock_tabs() {372bool update_menu = false;373for (EditorDock *dock : dirty_docks) {374update_menu = update_menu || dock->global;375dock->update_tab_style();376}377dirty_docks.clear();378379if (update_menu) {380update_docks_menu();381}382}383384void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {385// Save docks by dock slot.386for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {387dock_slots[i]->save_docks_to_config(p_layout, p_section);388}389390// Clear the special dock slot for docks without default slots (index -1 = dock_0).391// This prevents closed docks from being infinitely appended to the config on each save.392const String no_slot_config_key = "dock_0";393if (p_layout->has_section_key(p_section, no_slot_config_key)) {394p_layout->erase_section_key(p_section, no_slot_config_key);395}396397// Save docks in windows.398Dictionary floating_docks_dump;399for (WindowWrapper *wrapper : dock_windows) {400EditorDock *dock = Object::cast_to<EditorDock>(wrapper->get_wrapped_control());401402Dictionary window_dump;403window_dump["window_rect"] = wrapper->get_window_rect();404405int screen = wrapper->get_window_screen();406window_dump["window_screen"] = wrapper->get_window_screen();407window_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);408409String name = dock->get_effective_layout_key();410if (!dock->transient) {411floating_docks_dump[name] = window_dump;412}413414// Append to regular dock section so we know where to restore it to.415int dock_slot_id = dock->dock_slot_index;416String config_key = DockTabContainer::get_config_key(dock_slot_id);417418String names = p_layout->get_value(p_section, config_key, "");419if (names.is_empty()) {420names = name;421} else {422names += "," + name;423}424p_layout->set_value(p_section, config_key, names);425}426p_layout->set_value(p_section, "dock_floating", floating_docks_dump);427428Array closed_docks_dump;429for (const EditorDock *dock : all_docks) {430const String section_name = p_section + "/" + dock->get_effective_layout_key();431dock->save_layout_to_config(p_layout, section_name);432433if (dock->is_open) {434continue;435}436437// Save closed docks.438const String name = dock->get_effective_layout_key();439if (!dock->transient) {440closed_docks_dump.push_back(name);441}442443int dock_slot_id = dock->dock_slot_index;444String config_key = DockTabContainer::get_config_key(dock_slot_id);445446String names = p_layout->get_value(p_section, config_key, "");447if (names.is_empty()) {448names = name;449} else {450names += "," + name;451}452p_layout->set_value(p_section, config_key, names);453}454p_layout->set_value(p_section, "dock_closed", closed_docks_dump);455456// Save SplitContainer offsets.457for (int i = 0; i < vsplits.size(); i++) {458if (vsplits[i]->is_visible_in_tree()) {459p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());460}461}462463PackedInt32Array split_offsets = main_hsplit->get_split_offsets();464int index = 0;465for (int i = 0; i < vsplits.size(); i++) {466int value = 0;467if (vsplits[i]->is_visible() && index < split_offsets.size()) {468value = split_offsets[index] / EDSCALE;469index++;470}471p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), value);472}473}474475void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section, bool p_first_load) {476Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());477Array closed_docks = p_layout->get_value(p_section, "dock_closed", Array());478479bool allow_floating_docks = EditorNode::get_singleton()->is_multi_window_enabled() && (!p_first_load || EDITOR_GET("interface/multi_window/restore_windows_on_load"));480481// Store the docks by name for easy lookup.482HashMap<String, EditorDock *> dock_map;483for (EditorDock *dock : all_docks) {484dock_map[dock->get_effective_layout_key()] = dock;485}486487// Load docks by slot. Index -1 is for docks that have no slot.488for (int i = -1; i < EditorDock::DOCK_SLOT_MAX; i++) {489const String key = DockTabContainer::get_config_key(i);490if (!p_layout->has_section_key(p_section, key)) {491continue;492}493494Vector<String> names = String(p_layout->get_value(p_section, key)).split(",");495for (int j = names.size() - 1; j >= 0; j--) {496const String &name = names[j];497const String section_name = p_section + "/" + name;498499if (!dock_map.has(name)) {500continue;501}502EditorDock *dock = dock_map[name];503504if (!dock->enabled) {505// Don't open disabled docks.506dock->load_layout_from_config(p_layout, section_name);507continue;508}509510if (allow_floating_docks && floating_docks_dump.has(name)) {511_restore_dock_to_saved_window(dock, floating_docks_dump[name]);512} else if (i >= 0 && !(dock->transient && !dock->is_open)) {513// Safe to include transient open docks here because they won't be in the closed dock dump.514if (closed_docks.has(name)) {515dock->is_open = false;516dock->hide();517_move_dock(dock, closed_dock_parent);518} else {519dock->is_open = true;520_move_dock(dock, dock_slots[i], 0);521}522}523dock->load_layout_from_config(p_layout, section_name);524525dock->dock_slot_index = i;526dock->previous_tab_index = i >= 0 ? j : 0;527}528}529530// Set the selected tabs.531for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {532int selected_tab_idx = p_layout->get_value(p_section, DockTabContainer::get_config_key(i) + "_selected_tab_idx", -1);533dock_slots[i]->load_selected_tab(selected_tab_idx);534}535536// Load SplitContainer offsets.537PackedInt32Array offsets;538for (int i = 0; i < vsplits.size(); i++) {539if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {540continue;541}542int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));543vsplits[i]->set_split_offset(ofs);544545// Only visible ones need a split offset for the main hsplit, even though they all have a value saved.546if (vsplits[i]->is_visible() && p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {547int offset = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));548offsets.push_back(offset * EDSCALE);549}550}551main_hsplit->set_split_offsets(offsets);552553update_docks_menu();554}555556void EditorDockManager::set_dock_enabled(EditorDock *p_dock, bool p_enabled) {557ERR_FAIL_NULL(p_dock);558ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set enabled unknown dock '%s'.", p_dock->get_display_title()));559560if (p_dock->enabled == p_enabled) {561return;562}563564p_dock->enabled = p_enabled;565if (p_enabled) {566open_dock(p_dock, false);567} else {568close_dock(p_dock);569}570}571572void EditorDockManager::close_dock(EditorDock *p_dock) {573ERR_FAIL_NULL(p_dock);574ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot close unknown dock '%s'.", p_dock->get_display_title()));575576if (!p_dock->is_open) {577return;578}579580p_dock->is_open = false;581DockTabContainer *parent_container = p_dock->get_parent_container();582if (parent_container) {583parent_container->dock_closed(p_dock);584}585586// Hide before moving to remove inconsistent signals.587p_dock->hide();588_move_dock(p_dock, closed_dock_parent);589590_update_layout();591}592593void EditorDockManager::open_dock(EditorDock *p_dock, bool p_set_current) {594ERR_FAIL_NULL(p_dock);595ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot open unknown dock '%s'.", p_dock->get_display_title()));596597if (p_dock->is_open) {598// Show the dock if it is already open.599if (p_set_current) {600_make_dock_visible(p_dock, false);601}602return;603}604605p_dock->is_open = true;606607// Open dock to its previous location.608if (p_dock->dock_slot_index != EditorDock::DOCK_SLOT_NONE) {609DockTabContainer *slot = dock_slots[p_dock->dock_slot_index];610int tab_index = p_dock->previous_tab_index;611if (tab_index < 0) {612tab_index = slot->get_tab_count();613}614615_move_dock(p_dock, slot, tab_index, p_set_current && slot->can_switch_dock());616} else {617_open_dock_in_window(p_dock, true, true);618return;619}620621_update_layout();622}623624void EditorDockManager::make_dock_floating(EditorDock *p_dock) {625ERR_FAIL_NULL(p_dock);626ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot make unknown dock '%s' floating.", p_dock->get_display_title()));627628if (!p_dock->dock_window) {629_open_dock_in_window(p_dock);630}631}632633void EditorDockManager::_make_dock_visible(EditorDock *p_dock, bool p_grab_focus) {634if (p_dock->dock_window) {635if (p_grab_focus) {636p_dock->get_window()->grab_focus();637}638return;639}640641DockTabContainer *tab_container = p_dock->get_parent_container();642if (!tab_container || !tab_container->can_switch_dock()) {643return;644}645646if (p_grab_focus) {647tab_container->get_tab_bar()->grab_focus();648}649650int tab_index = tab_container->get_tab_idx_from_control(p_dock);651tab_container->set_current_tab(tab_index);652}653654void EditorDockManager::focus_dock(EditorDock *p_dock) {655ERR_FAIL_NULL(p_dock);656ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot focus unknown dock '%s'.", p_dock->get_display_title()));657658if (!p_dock->enabled) {659return;660}661662if (!p_dock->is_open) {663p_dock->emit_signal("opened");664open_dock(p_dock, false);665}666667_make_dock_visible(p_dock, true);668}669670void EditorDockManager::add_dock(EditorDock *p_dock) {671ERR_FAIL_NULL(p_dock);672ERR_FAIL_COND_MSG(all_docks.has(p_dock), vformat("Cannot add dock '%s', already added.", p_dock->get_display_title()));673674p_dock->dock_slot_index = p_dock->default_slot;675all_docks.push_back(p_dock);676p_dock->connect("_tab_style_changed", callable_mp(this, &EditorDockManager::_queue_update_tab_style).bind(p_dock));677p_dock->connect("renamed", callable_mp(this, &EditorDockManager::_queue_update_tab_style).bind(p_dock));678679if (p_dock->default_slot != EditorDock::DOCK_SLOT_NONE) {680open_dock(p_dock, false);681} else {682closed_dock_parent->add_child(p_dock);683p_dock->hide();684_update_layout();685}686}687688void EditorDockManager::remove_dock(EditorDock *p_dock) {689ERR_FAIL_NULL(p_dock);690ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot remove unknown dock '%s'.", p_dock->get_display_title()));691692_move_dock(p_dock, nullptr);693694all_docks.erase(p_dock);695p_dock->disconnect("_tab_style_changed", callable_mp(this, &EditorDockManager::_queue_update_tab_style));696p_dock->disconnect("renamed", callable_mp(this, &EditorDockManager::_queue_update_tab_style));697_update_layout();698}699700void EditorDockManager::set_docks_visible(bool p_show) {701if (docks_visible == p_show) {702return;703}704docks_visible = p_show;705for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {706// Show and hide in reverse order due to the SplitContainer prioritizing the last split offset.707dock_slots[docks_visible ? i : EditorDock::DOCK_SLOT_MAX - i - 1]->update_visibility();708}709_update_layout();710}711712bool EditorDockManager::are_docks_visible() const {713return docks_visible;714}715716void EditorDockManager::update_tab_styles() {717for (EditorDock *dock : all_docks) {718dock->update_tab_style();719}720}721722void EditorDockManager::set_tab_icon_max_width(int p_max_width) {723for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {724dock_slots[i]->add_theme_constant_override(SNAME("icon_max_width"), p_max_width);725}726}727728void EditorDockManager::add_vsplit(DockSplitContainer *p_split) {729vsplits.push_back(p_split);730p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));731}732733void EditorDockManager::set_hsplit(DockSplitContainer *p_split) {734main_hsplit = p_split;735p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));736}737738void EditorDockManager::register_dock_slot(DockTabContainer *p_tab_container) {739ERR_FAIL_NULL(p_tab_container);740dock_slots[p_tab_container->dock_slot] = p_tab_container;741742p_tab_container->set_dock_context_popup(dock_context_popup);743p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));744p_tab_container->connect("active_tab_rearranged", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));745}746747int EditorDockManager::get_vsplit_count() const {748return vsplits.size();749}750751PopupMenu *EditorDockManager::get_docks_menu() {752return docks_menu;753}754755EditorDockManager::EditorDockManager() {756singleton = this;757758closed_dock_parent = memnew(Control);759closed_dock_parent->hide();760EditorNode::get_singleton()->get_gui_base()->add_child(closed_dock_parent);761762dock_context_popup = memnew(DockContextPopup);763EditorNode::get_singleton()->get_gui_base()->add_child(dock_context_popup);764EditorNode::get_singleton()->add_child(memnew(DockShortcutHandler));765766docks_menu = memnew(PopupMenu);767docks_menu->set_hide_on_item_selection(false);768docks_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDockManager::_docks_menu_option));769EditorNode::get_singleton()->get_gui_base()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorDockManager::update_docks_menu));770}771772////////////////////////////////////////////////773////////////////////////////////////////////////774775void DockContextPopup::_notification(int p_what) {776switch (p_what) {777case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:778case NOTIFICATION_TRANSLATION_CHANGED:779case NOTIFICATION_THEME_CHANGED: {780if (make_float_button) {781make_float_button->set_button_icon(get_editor_theme_icon(SNAME("MakeFloating")));782}783if (is_layout_rtl()) {784tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));785tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));786tab_move_left_button->set_tooltip_text(TTR("Move this dock right one tab."));787tab_move_right_button->set_tooltip_text(TTR("Move this dock left one tab."));788} else {789tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));790tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));791tab_move_left_button->set_tooltip_text(TTR("Move this dock left one tab."));792tab_move_right_button->set_tooltip_text(TTR("Move this dock right one tab."));793}794close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));795} break;796}797}798799void DockContextPopup::_tab_move_left() {800TabContainer *tab_container = context_dock->get_parent_container();801if (!tab_container) {802return;803}804int new_index = tab_container->get_tab_idx_from_control(context_dock) - 1;805context_dock->set_tab_index(new_index, true);806dock_manager->_update_layout();807dock_select->queue_redraw();808}809810void DockContextPopup::_tab_move_right() {811TabContainer *tab_container = context_dock->get_parent_container();812if (!tab_container) {813return;814}815int new_index = tab_container->get_tab_idx_from_control(context_dock) + 1;816context_dock->set_tab_index(new_index, true);817dock_manager->_update_layout();818dock_select->queue_redraw();819}820821void DockContextPopup::_close_dock() {822hide();823context_dock->emit_signal("closed");824dock_manager->close_dock(context_dock);825}826827void DockContextPopup::_float_dock() {828hide();829dock_manager->_open_dock_in_window(context_dock);830}831832void DockContextPopup::_dock_select_input(const Ref<InputEvent> &p_input) {833Ref<InputEventMouse> me = p_input;834835if (me.is_valid()) {836Vector2 point = me->get_position();837838int over_dock_slot = -1;839for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {840if (dock_select_rects[i].has_point(point)) {841over_dock_slot = i;842break;843}844}845846if (over_dock_slot != dock_select_rect_over_idx) {847dock_select->queue_redraw();848dock_select_rect_over_idx = over_dock_slot;849}850851if (over_dock_slot == -1) {852return;853}854855Ref<InputEventMouseButton> mb = me;856DockTabContainer *target_tab_container = dock_manager->dock_slots[over_dock_slot];857if (context_dock->get_parent_container() == target_tab_container) {858return;859}860861if (!(context_dock->available_layouts & target_tab_container->layout)) {862return;863}864865if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {866dock_manager->_move_dock(context_dock, target_tab_container, target_tab_container->get_tab_count());867dock_manager->_update_layout();868hide();869}870}871}872873void DockContextPopup::_dock_select_mouse_exited() {874dock_select_rect_over_idx = -1;875dock_select->queue_redraw();876}877878void DockContextPopup::_dock_select_draw() {879Color used_dock_color = Color(0.6, 0.6, 0.6, 0.8);880Color hovered_dock_color = Color(0.8, 0.8, 0.8, 0.8);881Color tab_selected_color = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));882Color tab_unselected_color = used_dock_color;883Color unused_dock_color = used_dock_color;884unused_dock_color.a = 0.4;885Color unusable_dock_color = unused_dock_color;886unusable_dock_color.a = 0.1;887888// Update sizes.889Size2 dock_size = dock_select->get_size();890dock_size.x /= 6.0;891dock_size.y /= 2.0;892893real_t center_panel_width = dock_size.x * 2.0;894Rect2 center_panel_rect(center_panel_width, 0, center_panel_width, dock_size.y);895896if (dock_select->is_layout_rtl()) {897dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(), dock_size);898dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(0, dock_size.y), dock_size);899dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x, 0), dock_size);900dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BL] = Rect2(dock_size, dock_size);901dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x * 4, 0), dock_size);902dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BR] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);903dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UL] = Rect2(Point2(dock_size.x * 5, 0), dock_size);904dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BL] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);905} else {906dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UL] = Rect2(Point2(), dock_size);907dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BL] = Rect2(Point2(0, dock_size.y), dock_size);908dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x, 0), dock_size);909dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BR] = Rect2(dock_size, dock_size);910dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x * 4, 0), dock_size);911dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BL] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);912dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(dock_size.x * 5, 0), dock_size);913dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);914}915dock_select_rects[EditorDock::DOCK_SLOT_BOTTOM] = Rect2(center_panel_width, dock_size.y, center_panel_width, dock_size.y);916917int rtl_dir = dock_select->is_layout_rtl() ? -1 : 1;918real_t tab_height = 3.0 * EDSCALE;919real_t tab_spacing = 1.0 * EDSCALE;920real_t dock_spacing = 2.0 * EDSCALE;921real_t dock_top_spacing = tab_height + dock_spacing;922923TabContainer *context_tab_container = context_dock->get_parent_container();924int context_tab_index = -1;925if (context_tab_container && context_tab_container->get_tab_count() > 0) {926context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);927}928929// Draw center panel.930Rect2 center_panel_draw_rect = center_panel_rect.grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);931dock_select->draw_rect(center_panel_draw_rect, unusable_dock_color);932933// Draw all dock slots.934for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {935int max_tabs = (i == EditorDock::DOCK_SLOT_BOTTOM) ? 6 : 3;936const DockTabContainer *dock_slot = dock_manager->dock_slots[i];937938Rect2 dock_slot_draw_rect = dock_select_rects[i].grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);939real_t tab_width = Math::round(dock_slot_draw_rect.size.width / max_tabs);940Rect2 tab_draw_rect = Rect2(dock_slot_draw_rect.position.x, dock_select_rects[i].position.y, tab_width - tab_spacing, tab_height);941942real_t max_width = tab_width * max_tabs;943// Tabs may not fit perfectly, so they need to be re-centered.944if (max_width > dock_slot_draw_rect.size.x) {945tab_draw_rect.position.x -= int(max_width - dock_slot_draw_rect.size.x) / 2 * rtl_dir;946}947if (dock_select->is_layout_rtl()) {948tab_draw_rect.position.x += dock_slot_draw_rect.size.x - tab_draw_rect.size.x;949}950951int tabs_to_draw = MIN(max_tabs, dock_slot->get_tab_count());952bool is_context_dock = context_tab_container == dock_slot;953if (i == context_dock->dock_slot_index) {954dock_select->draw_rect(dock_slot_draw_rect, tab_selected_color);955} else if (!(context_dock->available_layouts & dock_slot->layout)) {956dock_select->draw_rect(dock_slot_draw_rect, unusable_dock_color);957} else if (i == dock_select_rect_over_idx) {958dock_select->draw_rect(dock_slot_draw_rect, hovered_dock_color);959} else if (tabs_to_draw == 0) {960dock_select->draw_rect(dock_slot_draw_rect, unused_dock_color);961} else {962dock_select->draw_rect(dock_slot_draw_rect, used_dock_color);963}964965// Draw tabs above each used dock slot.966for (int j = 0; j < tabs_to_draw; j++) {967Color tab_color = tab_unselected_color;968if (is_context_dock && context_tab_index == j) {969tab_color = tab_selected_color;970}971Rect2 tabj_draw_rect = tab_draw_rect;972tabj_draw_rect.position.x += tab_width * j * rtl_dir;973dock_select->draw_rect(tabj_draw_rect, tab_color);974}975}976}977978void DockContextPopup::_update_buttons() {979if (context_dock->global || context_dock->closable) {980close_button->set_tooltip_text(TTRC("Close this dock."));981close_button->set_disabled(false);982} else {983close_button->set_tooltip_text(TTRC("This dock can't be closed."));984close_button->set_disabled(true);985}986if (EditorNode::get_singleton()->is_multi_window_enabled()) {987if (!(context_dock->available_layouts & EditorDock::DOCK_LAYOUT_FLOATING)) {988make_float_button->set_tooltip_text(TTRC("This dock does not support floating."));989make_float_button->set_disabled(true);990} else {991make_float_button->set_tooltip_text(TTRC("Make this dock floating."));992make_float_button->set_disabled(false);993}994}995996// Update tab move buttons.997tab_move_left_button->set_disabled(true);998tab_move_right_button->set_disabled(true);999TabContainer *context_tab_container = context_dock->get_parent_container();1000if (context_tab_container && context_tab_container->get_tab_count() > 0) {1001int context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);1002tab_move_left_button->set_disabled(context_tab_index == 0);1003tab_move_right_button->set_disabled(context_tab_index >= context_tab_container->get_tab_count() - 1);1004}1005reset_size();1006}10071008void DockContextPopup::set_dock(EditorDock *p_dock) {1009context_dock = p_dock;1010_update_buttons();1011}10121013void DockContextPopup::docks_updated() {1014if (!is_visible()) {1015return;1016}1017_update_buttons();1018}10191020DockContextPopup::DockContextPopup() {1021dock_manager = EditorDockManager::get_singleton();10221023dock_select_popup_vb = memnew(VBoxContainer);1024add_child(dock_select_popup_vb);10251026HBoxContainer *header_hb = memnew(HBoxContainer);1027tab_move_left_button = memnew(Button);1028tab_move_left_button->set_accessibility_name(TTRC("Move Tab Left"));1029tab_move_left_button->set_flat(true);1030tab_move_left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1031tab_move_left_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_left));1032header_hb->add_child(tab_move_left_button);10331034Label *position_label = memnew(Label);1035position_label->set_text(TTRC("Dock Position"));1036position_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);1037position_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1038header_hb->add_child(position_label);10391040tab_move_right_button = memnew(Button);1041tab_move_right_button->set_accessibility_name(TTRC("Move Tab Right"));1042tab_move_right_button->set_flat(true);1043tab_move_right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1044tab_move_right_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_right));10451046header_hb->add_child(tab_move_right_button);1047dock_select_popup_vb->add_child(header_hb);10481049dock_select = memnew(Control);1050dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);1051dock_select->connect(SceneStringName(gui_input), callable_mp(this, &DockContextPopup::_dock_select_input));1052dock_select->connect(SceneStringName(draw), callable_mp(this, &DockContextPopup::_dock_select_draw));1053dock_select->connect(SceneStringName(mouse_exited), callable_mp(this, &DockContextPopup::_dock_select_mouse_exited));1054dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);1055dock_select_popup_vb->add_child(dock_select);10561057make_float_button = memnew(Button);1058make_float_button->set_text(TTRC("Make Floating"));1059if (!EditorNode::get_singleton()->is_multi_window_enabled()) {1060make_float_button->set_disabled(true);1061make_float_button->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());1062}1063make_float_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1064make_float_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);1065make_float_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_float_dock));1066dock_select_popup_vb->add_child(make_float_button);10671068close_button = memnew(Button);1069close_button->set_text(TTRC("Close"));1070close_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1071close_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);1072close_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_close_dock));1073dock_select_popup_vb->add_child(close_button);1074}10751076void DockShortcutHandler::shortcut_input(const Ref<InputEvent> &p_event) {1077if (p_event.is_null() || !p_event->is_pressed() || p_event->is_echo()) {1078return;1079}10801081for (EditorDock *dock : EditorDockManager::get_singleton()->all_docks) {1082const Ref<Shortcut> &dock_shortcut = dock->get_dock_shortcut();1083if (dock_shortcut.is_valid() && dock_shortcut->matches_event(p_event)) {1084bool was_visible = dock->is_visible();1085if (!dock->transient || dock->is_open) {1086EditorDockManager::get_singleton()->focus_dock(dock);1087}1088DockTabContainer *dock_container = dock->get_parent_container();1089if (dock_container) {1090dock_container->dock_focused(dock, was_visible);1091}1092get_viewport()->set_input_as_handled();1093break;1094}1095}1096}109710981099