diff options
author | trollodel <33117082+trollodel@users.noreply.github.com> | 2022-11-02 15:23:25 +0100 |
---|---|---|
committer | trollodel <33117082+trollodel@users.noreply.github.com> | 2023-05-10 09:14:21 +0200 |
commit | b4d6b47c17f76f3d02fc11cc973a373769b19619 (patch) | |
tree | 23bc6395aaf2af208f5744580ce7100d74cd9f55 /editor | |
parent | 769d8a7bbe6f59a8a7cae0194b65bf078c9bb2b4 (diff) | |
download | redot-engine-b4d6b47c17f76f3d02fc11cc973a373769b19619.tar.gz |
Add multi window code and shader editors
Diffstat (limited to 'editor')
-rw-r--r-- | editor/editor_help_search.cpp | 5 | ||||
-rw-r--r-- | editor/editor_node.cpp | 191 | ||||
-rw-r--r-- | editor/editor_node.h | 9 | ||||
-rw-r--r-- | editor/editor_settings.cpp | 6 | ||||
-rw-r--r-- | editor/editor_themes.cpp | 2 | ||||
-rw-r--r-- | editor/icons/MakeFloating.svg | 1 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.cpp | 136 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.h | 21 | ||||
-rw-r--r-- | editor/plugins/shader_editor_plugin.cpp | 67 | ||||
-rw-r--r-- | editor/plugins/shader_editor_plugin.h | 8 | ||||
-rw-r--r-- | editor/window_wrapper.cpp | 474 | ||||
-rw-r--r-- | editor/window_wrapper.h | 110 |
12 files changed, 936 insertions, 94 deletions
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 204d918989..6aa508f40e 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 864d45230a..dbd0905199 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 <stdio.h> #include <stdlib.h> @@ -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<Window *>(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); - _update_dock_containers(); + floating_docks.erase(p_wrapper); + p_wrapper->queue_free(); - floating_docks.erase(p_control); + _update_dock_containers(); _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; - - int dock_index = dock->get_index(false); - dock_slot[dock_popup_selected_idx]->remove_child(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); + Size2 dock_size = p_dock->get_size() + borders * 2; + Point2 dock_screen_pos = p_dock->get_screen_position(); + + int dock_index = p_dock->get_index() - 1; + dock_slot[p_slot_index]->remove_child(p_dock); + + WindowWrapper *wrapper = memnew(WindowWrapper); + wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), p_dock->get_name())); + wrapper->set_margins_enabled(true); + + gui_base->add_child(wrapper); + + wrapper->set_wrapped_control(p_dock); + wrapper->set_meta("dock_slot", p_slot_index); + wrapper->set_meta("dock_index", dock_index); + wrapper->set_meta("dock_name", p_dock->get_name().operator String()); + + 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<ConfigFile> 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<WindowWrapper>(p_dock); + if (!wrapper) { + _dock_make_float(p_dock, p_slot_index, false); + wrapper = floating_docks[floating_docks.size() - 1]; + } + + wrapper->restore_window_from_saved_position( + p_dock_dump.get("window_rect", Rect2i()), + p_dock_dump.get("window_screen", -1), + p_dock_dump.get("window_screen_rect", Rect2i())); +} + void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) { + Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary()); + + bool restore_window_on_load = EDITOR_GET("interface/multi_window/restore_windows_on_load"); + for (int i = 0; i < DOCK_SLOT_MAX; i++) { if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) { continue; @@ -4928,6 +4974,7 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> 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<ConfigFile> 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); - 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(); + } - if (dock_slot[atidx]->get_tab_count() == 0) { - dock_slot[atidx]->hide(); + WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(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 0003fee301..c6fe70fc8d 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<Control *> floating_docks; + Vector<WindowWrapper *> 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<ConfigFile> p_layout, const String &p_section); + void _restore_floating_dock(const Dictionary &p_dock_dump, Control *p_wrapper, int p_slot_index); void _load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section); void _update_dock_slots_visibility(bool p_keep_selected_tabs = false); void _dock_tab_changed(int p_tab); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 9577cd0e63..5de2a47e0e 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -485,6 +485,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> 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 1b5144af67..3cdd78dc7f 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -833,6 +833,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> 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 0000000000..57ccce38ea --- /dev/null +++ b/editor/icons/MakeFloating.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 4.2333333 4.2333333" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m1.2907908.14089245c-.258841 0-.46866132.20982028-.46866132.46866151v.23432634h3.28061252v-.23432634c0-.25884123-.2098291-.46866151-.4686613-.46866151zm2.1089635.23433517h.2343264v.23432634h-.2343264zm-2.57762482.70298788v1.8746284c0 .2588412.20982912.4686614.46866132.4686614h2.3432899c.258841 0 .4686613-.2098202.4686613-.4686614v-1.8746284z" stroke-width=".23433"/><path d="m12.189144-6.0533422 5.5-5.4999998-2.44-2.439h7v6.9999998l-2.439-2.439-5.5 5.5z" stroke="#000" stroke-width="1.01435" transform="matrix(.19814944 0 0 .19814944 -2.163454 4.759098)"/></g></svg> diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index c605844728..ac9b2a692d 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<Script>(p_object)) { Script *p_script = Object::cast_to<Script>(p_object); @@ -4119,17 +4172,20 @@ bool ScriptEditorPlugin::handles(Object *p_object) const { void ScriptEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - script_editor->show(); + window_wrapper->show(); script_editor->set_process(true); script_editor->ensure_select_current(); } else { - script_editor->hide(); - script_editor->set_process(false); + window_wrapper->hide(); + if (!window_wrapper->get_window_enabled()) { + script_editor->set_process(false); + } } } void ScriptEditorPlugin::selected_notify() { script_editor->ensure_select_current(); + _focus_another_editor(); } void ScriptEditorPlugin::save_external_data() { @@ -4142,10 +4198,37 @@ void ScriptEditorPlugin::apply_changes() { void ScriptEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { script_editor->set_window_layout(p_layout); + + if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ScriptEditor", "window_rect")) { + window_wrapper->restore_window_from_saved_position( + p_layout->get_value("ScriptEditor", "window_rect", Rect2i()), + p_layout->get_value("ScriptEditor", "window_screen", -1), + p_layout->get_value("ScriptEditor", "window_screen_rect", Rect2i())); + } else { + window_wrapper->set_window_enabled(false); + } } void ScriptEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { script_editor->get_window_layout(p_layout); + + if (window_wrapper->get_window_enabled()) { + p_layout->set_value("ScriptEditor", "window_rect", window_wrapper->get_window_rect()); + int screen = window_wrapper->get_window_screen(); + p_layout->set_value("ScriptEditor", "window_screen", screen); + p_layout->set_value("ScriptEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen)); + + } else { + if (p_layout->has_section_key("ScriptEditor", "window_rect")) { + p_layout->erase_section_key("ScriptEditor", "window_rect"); + } + if (p_layout->has_section_key("ScriptEditor", "window_screen")) { + p_layout->erase_section_key("ScriptEditor", "window_screen"); + } + if (p_layout->has_section_key("ScriptEditor", "window_screen_rect")) { + p_layout->erase_section_key("ScriptEditor", "window_screen_rect"); + } + } } void ScriptEditorPlugin::get_breakpoints(List<String> *p_breakpoints) { @@ -4157,11 +4240,18 @@ void ScriptEditorPlugin::edited_scene_changed() { } ScriptEditorPlugin::ScriptEditorPlugin() { - script_editor = memnew(ScriptEditor); - EditorNode::get_singleton()->get_main_screen_control()->add_child(script_editor); - script_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - - script_editor->hide(); + window_wrapper = memnew(WindowWrapper); + window_wrapper->set_window_title(TTR("Script Editor - Godot Engine")); + window_wrapper->set_margins_enabled(true); + + script_editor = memnew(ScriptEditor(window_wrapper)); + Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("script_editor/make_floating", TTR("Make Floating")); + window_wrapper->set_wrapped_control(script_editor, make_floating_shortcut); + + EditorNode::get_singleton()->get_main_screen_control()->add_child(window_wrapper); + window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL); + window_wrapper->hide(); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditorPlugin::_window_visibility_changed)); EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"); ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save", true)); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 5cbe56b68b..e879920e41 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -47,6 +47,7 @@ class TabContainer; class TextureRect; class Tree; class VSplitContainer; +class WindowWrapper; class EditorSyntaxHighlighter : public SyntaxHighlighter { GDCLASS(EditorSyntaxHighlighter, SyntaxHighlighter) @@ -236,7 +237,7 @@ class ScriptEditor : public PanelContainer { WINDOW_NEXT, WINDOW_PREV, WINDOW_SORT, - WINDOW_SELECT_BASE = 100 + WINDOW_SELECT_BASE = 100, }; enum { @@ -272,6 +273,7 @@ class ScriptEditor : public PanelContainer { Button *help_search = nullptr; Button *site_search = nullptr; + Button *make_floating = nullptr; EditorHelpSearch *help_search_dialog = nullptr; ItemList *script_list = nullptr; @@ -308,6 +310,8 @@ class ScriptEditor : public PanelContainer { FindInFilesPanel *find_in_files = nullptr; Button *find_in_files_button = nullptr; + WindowWrapper *window_wrapper = nullptr; + enum { SCRIPT_EDITOR_FUNC_MAX = 32, }; @@ -479,6 +483,8 @@ class ScriptEditor : public PanelContainer { void _start_find_in_files(bool with_replace); void _on_find_in_files_modified_files(PackedStringArray paths); + void _window_changed(bool p_visible); + static void _open_script_request(const String &p_path); void _close_builtin_scripts_from_scene(const String &p_scene); @@ -538,7 +544,7 @@ public: static void register_create_script_editor_function(CreateScriptEditorFunc p_func); - ScriptEditor(); + ScriptEditor(WindowWrapper *p_wrapper); ~ScriptEditor(); }; @@ -546,6 +552,17 @@ class ScriptEditorPlugin : public EditorPlugin { GDCLASS(ScriptEditorPlugin, EditorPlugin); ScriptEditor *script_editor = nullptr; + WindowWrapper *window_wrapper = nullptr; + + String last_editor; + + void _focus_another_editor(); + + void _save_last_editor(String p_editor); + void _window_visibility_changed(bool p_visible); + +protected: + void _notification(int p_what); public: virtual String get_name() const override { return "Script"; } diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 5cb014e5c7..abbd61ad89 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -30,6 +30,7 @@ #include "shader_editor_plugin.h" +#include "editor/editor_command_palette.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_undo_redo_manager.h" @@ -38,6 +39,7 @@ #include "editor/plugins/text_shader_editor.h" #include "editor/plugins/visual_shader_editor_plugin.h" #include "editor/shader_create_dialog.h" +#include "editor/window_wrapper.h" #include "scene/gui/item_list.h" #include "scene/gui/texture_rect.h" @@ -171,13 +173,44 @@ bool ShaderEditorPlugin::handles(Object *p_object) const { void ShaderEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - EditorNode::get_singleton()->make_bottom_panel_item_visible(main_split); + EditorNode::get_singleton()->make_bottom_panel_item_visible(window_wrapper); } } void ShaderEditorPlugin::selected_notify() { } +void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { + if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ShaderEditor", "window_rect")) { + window_wrapper->restore_window_from_saved_position( + p_layout->get_value("ShaderEditor", "window_rect", Rect2i()), + p_layout->get_value("ShaderEditor", "window_screen", -1), + p_layout->get_value("ShaderEditor", "window_screen_rect", Rect2i())); + } else { + window_wrapper->set_window_enabled(false); + } +} + +void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { + if (window_wrapper->get_window_enabled()) { + p_layout->set_value("ShaderEditor", "window_rect", window_wrapper->get_window_rect()); + int screen = window_wrapper->get_window_screen(); + p_layout->set_value("ShaderEditor", "window_screen", screen); + p_layout->set_value("ShaderEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen)); + + } else { + if (p_layout->has_section_key("ShaderEditor", "window_rect")) { + p_layout->erase_section_key("ShaderEditor", "window_rect"); + } + if (p_layout->has_section_key("ShaderEditor", "window_screen")) { + p_layout->erase_section_key("ShaderEditor", "window_screen"); + } + if (p_layout->has_section_key("ShaderEditor", "window_screen_rect")) { + p_layout->erase_section_key("ShaderEditor", "window_screen_rect"); + } + } +} + TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { for (EditedShader &edited_shader : edited_shaders) { if (edited_shader.shader == p_for_shader) { @@ -450,6 +483,10 @@ void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_da } } +void ShaderEditorPlugin::_window_changed(bool p_visible) { + make_floating->set_visible(!p_visible); +} + void ShaderEditorPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -459,12 +496,18 @@ void ShaderEditorPlugin::_notification(int p_what) { } ShaderEditorPlugin::ShaderEditorPlugin() { + window_wrapper = memnew(WindowWrapper); + window_wrapper->set_window_title(TTR("Shader Editor - Godot Engine")); + window_wrapper->set_margins_enabled(true); + main_split = memnew(HSplitContainer); + Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTR("Make Floating")); + window_wrapper->set_wrapped_control(main_split, make_floating_shortcut); VBoxContainer *vb = memnew(VBoxContainer); - HBoxContainer *file_hb = memnew(HBoxContainer); - vb->add_child(file_hb); + HBoxContainer *menu_hb = memnew(HBoxContainer); + vb->add_child(menu_hb); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW); @@ -479,12 +522,26 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_item(TTR("Close File"), FILE_CLOSE); file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed)); - file_hb->add_child(file_menu); + menu_hb->add_child(file_menu); for (int i = FILE_SAVE; i < FILE_MAX; i++) { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); } + if (window_wrapper->is_window_available()) { + Control *padding = memnew(Control); + padding->set_h_size_flags(Control::SIZE_EXPAND_FILL); + menu_hb->add_child(padding); + + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->set_tooltip_text(TTR("Make the shader 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); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed)); + } + shader_list = memnew(ItemList); shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); vb->add_child(shader_list); @@ -503,7 +560,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { empty.instantiate(); shader_tabs->add_theme_style_override("panel", empty); - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader Editor"), main_split); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader Editor"), window_wrapper); // Defer connect because Editor class is not in the binding system yet. EditorNode::get_singleton()->call_deferred("connect", "resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED); diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 299d5975d2..bc508976ca 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -40,6 +40,7 @@ class ShaderCreateDialog; class TabContainer; class TextShaderEditor; class VisualShaderEditor; +class WindowWrapper; class ShaderEditorPlugin : public EditorPlugin { GDCLASS(ShaderEditorPlugin, EditorPlugin); @@ -74,6 +75,9 @@ class ShaderEditorPlugin : public EditorPlugin { Button *button = nullptr; MenuButton *file_menu = nullptr; + WindowWrapper *window_wrapper = nullptr; + Button *make_floating = nullptr; + ShaderCreateDialog *shader_create_dialog = nullptr; void _update_shader_list(); @@ -93,6 +97,8 @@ class ShaderEditorPlugin : public EditorPlugin { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void _window_changed(bool p_visible); + protected: void _notification(int p_what); @@ -102,6 +108,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; virtual void selected_notify() override; + virtual void set_window_layout(Ref<ConfigFile> p_layout) override; + virtual void get_window_layout(Ref<ConfigFile> p_layout) override; TextShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader); VisualShaderEditor *get_visual_shader_editor(const Ref<Shader> &p_for_shader); diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp new file mode 100644 index 0000000000..16cafe5736 --- /dev/null +++ b/editor/window_wrapper.cpp @@ -0,0 +1,474 @@ +/**************************************************************************/ +/* window_wrapper.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "window_wrapper.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/panel.h" +#include "scene/gui/popup.h" +#include "scene/main/window.h" + +// WindowWrapper + +// Capture all shortcut events not handled by other nodes. +class ShortcutBin : public Node { + GDCLASS(ShortcutBin, Node); + + virtual void _notification(int what) { + switch (what) { + case NOTIFICATION_READY: + set_process_shortcut_input(true); + break; + } + } + + virtual void shortcut_input(const Ref<InputEvent> &p_event) override { + if (!get_window()->is_visible()) { + return; + } + Window *grandparent_window = get_window()->get_parent_visible_window(); + ERR_FAIL_COND(!grandparent_window); + + if (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventShortcut>(p_event.ptr())) { + // HACK: Propagate the window input to the editor main window to handle global shortcuts. + grandparent_window->push_unhandled_input(p_event); + + if (grandparent_window->is_input_handled()) { + get_viewport()->set_input_as_handled(); + } + } + } +}; + +Rect2 WindowWrapper::_get_default_window_rect() const { + // Assume that the control rect is the desidered one for the window. + return wrapped_control->get_screen_rect(); +} + +Node *WindowWrapper::_get_wrapped_control_parent() const { + if (margins) { + return margins; + } + return window; +} + +void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect) { + ERR_FAIL_NULL(wrapped_control); + + if (!is_window_available()) { + return; + } + + if (window->is_visible() == p_visible) { + if (p_visible) { + window->grab_focus(); + } + return; + } + + Node *parent = _get_wrapped_control_parent(); + + if (wrapped_control->get_parent() != parent) { + // Move the control to the window. + wrapped_control->reparent(parent, false); + + _set_window_rect(p_rect); + wrapped_control->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + } else if (!p_visible) { + // Remove control from window. + wrapped_control->reparent(this, false); + } + + window->set_visible(p_visible); + if (!p_visible) { + emit_signal("window_close_requested"); + } + emit_signal("window_visibility_changed", p_visible); +} + +void WindowWrapper::_set_window_rect(const Rect2 p_rect) { + // Set the window rect even when the window is maximized to have a good default size + // when the user remove the maximized mode. + window->set_position(p_rect.position); + window->set_size(p_rect.size); + + if (EDITOR_GET("interface/multi_window/maximize_window")) { + window->set_mode(Window::MODE_MAXIMIZED); + } +} + +void WindowWrapper::_bind_methods() { + ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible"))); + ADD_SIGNAL(MethodInfo("window_close_requested")); +} + +void WindowWrapper::_notification(int p_what) { + if (!is_window_available()) { + return; + } + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (get_window_enabled() && is_visible()) { + // Grab the focus when WindowWrapper.set_visible(true) is called + // and the window is showing. + window->grab_focus(); + } + } break; + case NOTIFICATION_READY: { + set_process_shortcut_input(true); + } break; + case NOTIFICATION_THEME_CHANGED: { + window_background->add_theme_style_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles")); + } break; + } +} + +void WindowWrapper::shortcut_input(const Ref<InputEvent> &p_event) { + if (enable_shortcut.is_valid() && enable_shortcut->matches_event(p_event)) { + set_window_enabled(true); + } +} + +void WindowWrapper::set_wrapped_control(Control *p_control, const Ref<Shortcut> &p_enable_shortcut) { + ERR_FAIL_NULL(p_control); + ERR_FAIL_COND(wrapped_control); + + wrapped_control = p_control; + enable_shortcut = p_enable_shortcut; + add_child(p_control); +} + +Control *WindowWrapper::get_wrapped_control() const { + return wrapped_control; +} + +Control *WindowWrapper::release_wrapped_control() { + set_window_enabled(false); + if (wrapped_control) { + Control *old_wrapped = wrapped_control; + wrapped_control->get_parent()->remove_child(wrapped_control); + wrapped_control = nullptr; + + return old_wrapped; + } + return nullptr; +} + +bool WindowWrapper::is_window_available() const { + return window != nullptr; +} + +bool WindowWrapper::get_window_enabled() const { + return is_window_available() ? window->is_visible() : false; +} + +void WindowWrapper::set_window_enabled(bool p_enabled) { + _set_window_enabled_with_rect(p_enabled, _get_default_window_rect()); +} + +Rect2i WindowWrapper::get_window_rect() const { + ERR_FAIL_COND_V(!get_window_enabled(), Rect2i()); + return Rect2i(window->get_position(), window->get_size()); +} + +int WindowWrapper::get_window_screen() const { + ERR_FAIL_COND_V(!get_window_enabled(), -1); + return window->get_current_screen(); +} + +void WindowWrapper::restore_window(const Rect2i &p_rect, int p_screen) { + ERR_FAIL_COND(!is_window_available()); + ERR_FAIL_INDEX(p_screen, DisplayServer::get_singleton()->get_screen_count()); + + _set_window_enabled_with_rect(true, p_rect); + window->set_current_screen(p_screen); +} + +void WindowWrapper::restore_window_from_saved_position(const Rect2 p_window_rect, int p_screen, const Rect2 p_screen_rect) { + ERR_FAIL_COND(!is_window_available()); + + Rect2 window_rect = p_window_rect; + int screen = p_screen; + Rect2 restored_screen_rect = p_screen_rect; + + if (screen < 0 || screen >= DisplayServer::get_singleton()->get_screen_count()) { + // Fallback to the main window screen if the saved screen is not available. + screen = get_window()->get_window_id(); + } + + Rect2i real_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen); + + if (restored_screen_rect == Rect2i()) { + // Fallback to the target screen rect. + restored_screen_rect = real_screen_rect; + } + + if (window_rect == Rect2i()) { + // Fallback to a standard rect. + window_rect = Rect2i(restored_screen_rect.position + restored_screen_rect.size / 4, restored_screen_rect.size / 2); + } + + // Adjust the window rect size in case the resolution changes. + Vector2 screen_ratio = Vector2(real_screen_rect.size) / Vector2(restored_screen_rect.size); + + // The screen positioning may change, so remove the original screen position. + window_rect.position -= restored_screen_rect.position; + window_rect = Rect2i(window_rect.position * screen_ratio, window_rect.size * screen_ratio); + window_rect.position += real_screen_rect.position; + + // All good, restore the window. + window->set_current_screen(p_screen); + if (window->is_visible()) { + _set_window_rect(window_rect); + } else { + _set_window_enabled_with_rect(true, window_rect); + } +} + +void WindowWrapper::enable_window_on_screen(int p_screen, bool p_auto_scale) { + int current_screen = Object::cast_to<Window>(get_viewport())->get_current_screen(); + int screen = p_screen < 0 ? current_screen : p_screen; + + bool auto_scale = p_auto_scale && !EDITOR_GET("interface/multi_window/maximize_window"); + + if (auto_scale && current_screen != screen) { + Rect2 control_rect = _get_default_window_rect(); + + Rect2i source_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(current_screen); + Rect2i dest_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen); + + // Adjust the window rect size in case the resolution changes. + Vector2 screen_ratio = Vector2(source_screen_rect.size) / Vector2(dest_screen_rect.size); + + // The screen positioning may change, so remove the original screen position. + control_rect.position -= source_screen_rect.position; + control_rect = Rect2i(control_rect.position * screen_ratio, control_rect.size * screen_ratio); + control_rect.position += dest_screen_rect.position; + + restore_window(control_rect, p_screen); + } else { + window->set_current_screen(p_screen); + set_window_enabled(true); + } +} + +void WindowWrapper::set_window_title(const String p_title) { + if (!is_window_available()) { + return; + } + window->set_title(p_title); +} + +void WindowWrapper::set_margins_enabled(bool p_enabled) { + if (!is_window_available()) { + return; + } + + if (!p_enabled && margins) { + margins->queue_free(); + margins = nullptr; + } else if (p_enabled && !margins) { + Size2 borders = Size2(4, 4) * EDSCALE; + margins = memnew(MarginContainer); + margins->add_theme_constant_override("margin_right", borders.width); + margins->add_theme_constant_override("margin_top", borders.height); + margins->add_theme_constant_override("margin_left", borders.width); + margins->add_theme_constant_override("margin_bottom", borders.height); + + window->add_child(margins); + margins->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + } +} + +WindowWrapper::WindowWrapper() { + if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || !EDITOR_GET("interface/multi_window/enable")) { + return; + } + + window = memnew(Window); + window->set_wrap_controls(true); + + add_child(window); + window->hide(); + + window->connect("close_requested", callable_mp(this, &WindowWrapper::set_window_enabled).bind(false)); + + ShortcutBin *capturer = memnew(ShortcutBin); + window->add_child(capturer); + + window_background = memnew(Panel); + window_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + window->add_child(window_background); +} + +// ScreenSelect + +void ScreenSelect::_build_advanced_menu() { + // Clear old screen list. + while (screen_list->get_child_count(false) > 0) { + Node *child = screen_list->get_child(0); + screen_list->remove_child(child); + child->queue_free(); + } + + // Populate screen list. + const real_t height = real_t(get_theme_font_size("font_size")) * 1.5; + + int current_screen = get_window()->get_current_screen(); + for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { + Button *button = memnew(Button); + + Size2 screen_size = Size2(DisplayServer::get_singleton()->screen_get_size(i)); + Size2 button_size = Size2(height * (screen_size.x / screen_size.y), height); + button->set_custom_minimum_size(button_size); + screen_list->add_child(button); + + button->set_text(itos(i)); + button->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); + button->set_tooltip_text(vformat(TTR("Make this panel floating in the screen %d."), i)); + + if (i == current_screen) { + Color accent_color = get_theme_color("accent_color", "Editor"); + button->add_theme_color_override("font_color", accent_color); + } + + button->connect("pressed", callable_mp(this, &ScreenSelect::_emit_screen_signal).bind(i)); + button->connect("pressed", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false)); + button->connect("pressed", callable_mp(static_cast<Window *>(popup), &Popup::hide)); + } +} + +void ScreenSelect::_emit_screen_signal(int p_screen_idx) { + emit_signal("request_open_in_screen", p_screen_idx); +} + +void ScreenSelect::_bind_methods() { + ADD_SIGNAL(MethodInfo("request_open_in_screen", PropertyInfo(Variant::INT, "screen"))); +} + +void ScreenSelect::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + connect("gui_input", callable_mp(this, &ScreenSelect::_handle_mouse_shortcut)); + } break; + case NOTIFICATION_THEME_CHANGED: { + set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("MakeFloating", "EditorIcons")); + popup_background->add_theme_style_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles")); + + const real_t popup_height = real_t(get_theme_font_size("font_size")) * 2.0; + popup->set_min_size(Size2(0, popup_height * 3)); + } break; + } +} + +void ScreenSelect::_handle_mouse_shortcut(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mouse_button = p_event; + if (mouse_button.is_valid()) { + if (mouse_button->is_pressed() && mouse_button->get_button_index() == MouseButton::LEFT) { + _emit_screen_signal(get_window()->get_current_screen()); + accept_event(); + } + } +} + +void ScreenSelect::_show_popup() { + // Adapted from /scene/gui/menu_button.cpp::show_popup + if (!get_viewport()) { + return; + } + + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + + popup->set_size(Size2(size.width, 0)); + Point2 gp = get_screen_position(); + gp.y += size.y; + if (is_layout_rtl()) { + gp.x += size.width - popup->get_size().width; + } + popup->set_position(gp); + popup->popup(); +} + +void ScreenSelect::pressed() { + if (popup->is_visible()) { + popup->hide(); + return; + } + + _build_advanced_menu(); + _show_popup(); +} + +ScreenSelect::ScreenSelect() { + set_text(TTR("Make Floating")); + set_tooltip_text(TTR("Make this panel floating.\nRight click to open the screen selector.")); + set_button_mask(MouseButtonMask::RIGHT); + set_flat(true); + set_toggle_mode(true); + set_focus_mode(FOCUS_NONE); + set_action_mode(ACTION_MODE_BUTTON_PRESS); + + // Create the popup. + const Size2 borders = Size2(4, 4) * EDSCALE; + + popup = memnew(Popup); + popup->connect("popup_hide", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false)); + add_child(popup); + + popup_background = memnew(Panel); + popup_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + popup->add_child(popup_background); + + MarginContainer *popup_root = memnew(MarginContainer); + popup_root->add_theme_constant_override("margin_right", borders.width); + popup_root->add_theme_constant_override("margin_top", borders.height); + popup_root->add_theme_constant_override("margin_left", borders.width); + popup_root->add_theme_constant_override("margin_bottom", borders.height); + popup->add_child(popup_root); + + VBoxContainer *vb = memnew(VBoxContainer); + vb->set_alignment(BoxContainer::ALIGNMENT_CENTER); + popup_root->add_child(vb); + + Label *description = memnew(Label(TTR("Select Screen"))); + description->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + vb->add_child(description); + + screen_list = memnew(HBoxContainer); + screen_list->set_alignment(BoxContainer::ALIGNMENT_CENTER); + vb->add_child(screen_list); + + popup_root->set_anchors_and_offsets_preset(PRESET_FULL_RECT); +} diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h new file mode 100644 index 0000000000..e8fcb13c92 --- /dev/null +++ b/editor/window_wrapper.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* window_wrapper.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef WINDOW_WRAPPER_H +#define WINDOW_WRAPPER_H + +#include "core/math/rect2.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/menu_button.h" + +class Window; +class HBoxContainer; + +class WindowWrapper : public MarginContainer { + GDCLASS(WindowWrapper, MarginContainer); + + Control *wrapped_control = nullptr; + MarginContainer *margins = nullptr; + Window *window = nullptr; + + Panel *window_background = nullptr; + + Ref<Shortcut> enable_shortcut; + + Rect2 _get_default_window_rect() const; + Node *_get_wrapped_control_parent() const; + + void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect); + void _set_window_rect(const Rect2 p_rect); + +protected: + static void _bind_methods(); + void _notification(int p_what); + + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + +public: + void set_wrapped_control(Control *p_control, const Ref<Shortcut> &p_enable_shortcut = Ref<Shortcut>()); + Control *get_wrapped_control() const; + Control *release_wrapped_control(); + + bool is_window_available() const; + + bool get_window_enabled() const; + void set_window_enabled(bool p_enabled); + + Rect2i get_window_rect() const; + int get_window_screen() const; + + void restore_window(const Rect2i &p_rect, int p_screen = -1); + void restore_window_from_saved_position(const Rect2 p_window_rect, int p_screen, const Rect2 p_screen_rect); + void enable_window_on_screen(int p_screen = -1, bool p_auto_scale = false); + + void set_window_title(const String p_title); + void set_margins_enabled(bool p_enabled); + + WindowWrapper(); +}; + +class ScreenSelect : public Button { + GDCLASS(ScreenSelect, Button); + + Popup *popup = nullptr; + Panel *popup_background = nullptr; + HBoxContainer *screen_list = nullptr; + + void _build_advanced_menu(); + + void _emit_screen_signal(int p_screen_idx); + void _handle_mouse_shortcut(const Ref<InputEvent> &p_event); + void _show_popup(); + +protected: + virtual void pressed() override; + static void _bind_methods(); + + void _notification(int p_what); + +public: + ScreenSelect(); +}; + +#endif // WINDOW_WRAPPER_H |