diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 8c27c1bc060..b45e03dc384 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -580,6 +580,17 @@ If [code]true[/code], display OpenType features marked as [code]hidden[/code] by the font file in the [Font] editor. + + If [code]true[/code], the multi window support in editor is enabled. The following panels can become dedicated windows (made floating): Docks, Script editor, and Shader editor. + [b]Note:[/b] When [member interface/editor/single_window_mode] is [code]true[/code], the multi window support is always disabled. + + + If [code]true[/code], when panels are made floating they will be maximized. + If [code]false[/code], when panels are made floating their position and size will match the ones when they are attached (excluding window border) to the editor window. + + + If [code]true[/code], the floating panel position, size, and screen will be saved on editor exit. On next launch the panels that were floating will be made floating in the saved positions, sizes and screens, if possible. + Controls when the Close (X) button is displayed on scene tabs at the top of the editor. diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 204d9189890..6aa508f40e0 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -117,8 +117,11 @@ void EditorHelpSearch::_notification(int p_what) { _update_icons(); } break; - case NOTIFICATION_ENTER_TREE: { + case NOTIFICATION_READY: { connect("confirmed", callable_mp(this, &EditorHelpSearch::_confirmed)); + } break; + + case NOTIFICATION_THEME_CHANGED: { _update_icons(); } break; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 864d45230a3..dbd09051993 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -147,6 +147,7 @@ #include "editor/project_settings_editor.h" #include "editor/register_exporters.h" #include "editor/scene_tree_dock.h" +#include "editor/window_wrapper.h" #include #include @@ -4470,67 +4471,66 @@ void EditorNode::_copy_warning(const String &p_str) { DisplayServer::get_singleton()->clipboard_set(warning->get_text()); } -void EditorNode::_dock_floating_close_request(Control *p_control) { - // Through the MarginContainer to the Window. - Window *window = static_cast(p_control->get_parent()->get_parent()); - int window_slot = window->get_meta("dock_slot"); +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"); - p_control->get_parent()->remove_child(p_control); - dock_slot[window_slot]->add_child(p_control); - dock_slot[window_slot]->move_child(p_control, MIN((int)window->get_meta("dock_index"), dock_slot[window_slot]->get_tab_count() - 1)); - dock_slot[window_slot]->set_current_tab(dock_slot[window_slot]->get_tab_idx_from_control(p_control)); - dock_slot[window_slot]->set_tab_title(dock_slot[window_slot]->get_tab_idx_from_control(p_control), TTRGET(p_control->get_name())); + // Give back the dock to the original owner. + Control *dock = p_wrapper->release_wrapped_control(); - window->queue_free(); + dock_slot[dock_slot_num]->add_child(dock); + dock_slot[dock_slot_num]->move_child(dock, MIN(dock_slot_index, dock_slot[dock_slot_num]->get_tab_count())); + dock_slot[dock_slot_num]->set_current_tab(dock_slot_index); + + floating_docks.erase(p_wrapper); + p_wrapper->queue_free(); _update_dock_containers(); - floating_docks.erase(p_control); - _edit_current(); } -void EditorNode::_dock_make_float() { +void EditorNode::_dock_make_selected_float() { Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control(); - ERR_FAIL_COND(!dock); + _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_COND(!p_dock); Size2 borders = Size2(4, 4) * EDSCALE; // Remember size and position before removing it from the main window. - Size2 dock_size = dock->get_size() + borders * 2; - Point2 dock_screen_pos = dock->get_global_position() + get_tree()->get_root()->get_position() - borders; + Size2 dock_size = p_dock->get_size() + borders * 2; + Point2 dock_screen_pos = p_dock->get_screen_position(); - int dock_index = dock->get_index(false); - dock_slot[dock_popup_selected_idx]->remove_child(dock); + int dock_index = p_dock->get_index() - 1; + dock_slot[p_slot_index]->remove_child(p_dock); - Window *window = memnew(Window); - window->set_title(TTRGET(dock->get_name())); - Panel *p = memnew(Panel); - p->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("PanelForeground"), SNAME("EditorStyles"))); - p->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - window->add_child(p); - MarginContainer *margin = memnew(MarginContainer); - margin->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - margin->add_theme_constant_override("margin_right", borders.width); - margin->add_theme_constant_override("margin_top", borders.height); - margin->add_theme_constant_override("margin_left", borders.width); - margin->add_theme_constant_override("margin_bottom", borders.height); - window->add_child(margin); - dock->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - margin->add_child(dock); - window->set_wrap_controls(true); - window->set_size(dock_size); - window->set_position(dock_screen_pos); - window->set_transient(true); - window->connect("close_requested", callable_mp(this, &EditorNode::_dock_floating_close_request).bind(dock)); - window->set_meta("dock_slot", dock_popup_selected_idx); - window->set_meta("dock_index", dock_index); - gui_base->add_child(window); + 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()); + + 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_containers(); - floating_docks.push_back(dock); + floating_docks.push_back(wrapper); _edit_current(); } @@ -4772,6 +4772,35 @@ void EditorNode::_save_docks_to_config(Ref p_layout, const String &p } } + 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); + p_layout->set_value(p_section, "dock_filesystem_split", FileSystemDock::get_singleton()->get_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()); @@ -4918,7 +4947,24 @@ void EditorNode::_dock_tab_changed(int p_tab) { } } +void EditorNode::_restore_floating_dock(const Dictionary &p_dock_dump, Control *p_dock, int p_slot_index) { + WindowWrapper *wrapper = Object::cast_to(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 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; @@ -4928,6 +4974,7 @@ void EditorNode::_load_docks_from_config(Ref p_layout, const String 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; @@ -4942,24 +4989,45 @@ void EditorNode::_load_docks_from_config(Ref p_layout, const String atidx = k; break; } - if (atidx == -1) { // Well, it's not anywhere. + + 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); + return; + } else { + _dock_floating_close_request(wrapper); + atidx = wrapper->get_meta("dock_index"); + } + } + } + + // Well, it's not anywhere. continue; } if (atidx == i) { dock_slot[i]->move_child(node, 0); - continue; + } else if (atidx != -1) { + dock_slot[atidx]->remove_child(node); + + if (dock_slot[atidx]->get_tab_count() == 0) { + dock_slot[atidx]->hide(); + } + dock_slot[i]->add_child(node); + dock_slot[i]->move_child(node, 0); + dock_slot[i]->set_tab_title(0, TTRGET(node->get_name())); + dock_slot[i]->show(); } - dock_slot[atidx]->remove_child(node); - - if (dock_slot[atidx]->get_tab_count() == 0) { - dock_slot[atidx]->hide(); + WindowWrapper *wrapper = Object::cast_to(node); + if (restore_window_on_load && floating_docks_dump.has(name)) { + _restore_floating_dock(floating_docks_dump[name], node, i); + } else if (wrapper) { + _dock_floating_close_request(wrapper); } - dock_slot[i]->add_child(node); - dock_slot[i]->move_child(node, 0); - dock_slot[i]->set_tab_title(0, TTRGET(node->get_name())); - dock_slot[i]->show(); } } @@ -6824,13 +6892,16 @@ EditorNode::EditorNode() { dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL); dock_vb->add_child(dock_select); - 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, &EditorNode::_dock_make_float)); + if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && EDITOR_GET("interface/multi_window/enable")) { + dock_float = memnew(Button); + dock_float->set_icon(theme->get_icon("MakeFloating", "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_vb->add_child(dock_float); + } dock_select_popup->reset_size(); diff --git a/editor/editor_node.h b/editor/editor_node.h index 0003fee3014..c6fe70fc8df 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -113,6 +113,7 @@ class ProjectSettingsEditor; class RunSettingsDialog; class SceneImportSettings; class ScriptCreateDialog; +class WindowWrapper; class EditorNode : public Node { GDCLASS(EditorNode, Node); @@ -420,7 +421,7 @@ private: Button *new_inherited_button = nullptr; String open_import_request; - Vector floating_docks; + Vector floating_docks; Button *dock_float = nullptr; Button *dock_tab_move_left = nullptr; @@ -628,8 +629,9 @@ private: void _dock_pre_popup(int p_which); void _dock_split_dragged(int ofs); void _dock_popup_exit(); - void _dock_floating_close_request(Control *p_control); - void _dock_make_float(); + 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 _scene_tab_changed(int p_tab); void _proceed_closing_scene_tabs(); bool _is_closing_editor() const; @@ -649,6 +651,7 @@ private: void _save_docks(); void _load_docks(); void _save_docks_to_config(Ref 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 p_layout, const String &p_section); void _update_dock_slots_visibility(bool p_keep_selected_tabs = false); void _dock_tab_changed(int p_tab); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 9577cd0e634..5de2a47e0e3 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -485,6 +485,12 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "interface/scene_tabs/maximum_width", 350, "0,9999,1", PROPERTY_USAGE_DEFAULT) _initial_set("interface/scene_tabs/show_script_button", false); + // Multi Window + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/enable", true, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/restore_windows_on_load", true, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/maximize_window", false, ""); + set_restart_if_changed("interface/multi_window/enable", true); + /* Filesystem */ // External Programs diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1b5144af670..3cdd78dc7ff 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -833,6 +833,8 @@ Ref create_editor_theme(const Ref p_theme) { // Script Editor theme->set_stylebox("ScriptEditorPanel", "EditorStyles", make_empty_stylebox(default_margin_size, 0, default_margin_size, default_margin_size)); + theme->set_stylebox("ScriptEditorPanelFloating", "EditorStyles", make_empty_stylebox(0, 0, 0, 0)); + theme->set_stylebox("ScriptEditor", "EditorStyles", make_empty_stylebox(0, 0, 0, 0)); // Launch Pad and Play buttons diff --git a/editor/icons/MakeFloating.svg b/editor/icons/MakeFloating.svg new file mode 100644 index 00000000000..57ccce38eae --- /dev/null +++ b/editor/icons/MakeFloating.svg @@ -0,0 +1 @@ + diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index c605844728c..ac9b2a692df 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -40,7 +40,9 @@ #include "core/version.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" +#include "editor/editor_command_palette.h" #include "editor/editor_help_search.h" +#include "editor/editor_interface.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_scale.h" @@ -54,6 +56,8 @@ #include "editor/node_dock.h" #include "editor/plugins/shader_editor_plugin.h" #include "editor/plugins/text_shader_editor.h" +#include "editor/window_wrapper.h" +#include "scene/main/node.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" #include "script_text_editor.h" @@ -1584,23 +1588,10 @@ void ScriptEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); - EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); - EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); - EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); - FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); - FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); - script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); - - members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected)); - help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected)); - script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); - list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); - - EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); - EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); _editor_settings_changed(); [[fallthrough]]; } + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { @@ -1635,6 +1626,20 @@ void ScriptEditor::_notification(int p_what) { InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open)); EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search)); EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ScriptEditor::_close_builtin_scripts_from_scene)); + EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); + FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); + FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); + script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); + + members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected)); + help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected)); + script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); + list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); + + EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); + EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); } break; case NOTIFICATION_EXIT_TREE: { @@ -3711,6 +3716,10 @@ void ScriptEditor::_on_find_in_files_modified_files(PackedStringArray paths) { _update_modified_scripts_for_external_editor(); } +void ScriptEditor::_window_changed(bool p_visible) { + make_floating->set_visible(!p_visible); +} + void ScriptEditor::_filter_scripts_text_changed(const String &p_newtext) { _update_script_names(); } @@ -3747,7 +3756,8 @@ void ScriptEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script"))); } -ScriptEditor::ScriptEditor() { +ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { + window_wrapper = p_wrapper; current_theme = ""; script_editor_cache.instantiate(); @@ -3973,6 +3983,16 @@ ScriptEditor::ScriptEditor() { menu_hb->add_child(help_search); help_search->set_tooltip_text(TTR("Search the reference documentation.")); + if (p_wrapper->is_window_available()) { + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->set_tooltip_text(TTR("Make the script editor floating.")); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + + menu_hb->add_child(make_floating); + p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed)); + } + menu_hb->add_child(memnew(VSeparator)); script_back = memnew(Button); @@ -4079,6 +4099,39 @@ ScriptEditor::~ScriptEditor() { memdelete(completion_cache); } +void ScriptEditorPlugin::_focus_another_editor() { + if (window_wrapper->get_window_enabled()) { + ERR_FAIL_COND(last_editor.is_empty()); + EditorInterface::get_singleton()->set_main_screen_editor(last_editor); + } +} + +void ScriptEditorPlugin::_save_last_editor(String p_editor) { + if (p_editor != get_name()) { + last_editor = p_editor; + } +} + +void ScriptEditorPlugin::_window_visibility_changed(bool p_visible) { + _focus_another_editor(); + if (p_visible) { + script_editor->add_theme_style_override("panel", script_editor->get_theme_stylebox("ScriptEditorPanelFloating", "EditorStyles")); + } else { + script_editor->add_theme_style_override("panel", script_editor->get_theme_stylebox("ScriptEditorPanel", "EditorStyles")); + } +} + +void ScriptEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + connect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor)); + } break; + case NOTIFICATION_EXIT_TREE: { + disconnect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor)); + } break; + } +} + void ScriptEditorPlugin::edit(Object *p_object) { if (Object::cast_to