From 10ac7baae28f29a31ea8d450838f09586c9f3c7a Mon Sep 17 00:00:00 2001 From: Michael Alexsander Date: Wed, 25 Mar 2026 19:31:22 -0300 Subject: [PATCH] Fix and improve the editor layout dialog --- editor/editor_node.cpp | 69 +++++------ editor/editor_node.h | 2 +- editor/settings/editor_layouts_dialog.cpp | 132 +++++++++++++++------- editor/settings/editor_layouts_dialog.h | 11 +- 4 files changed, 136 insertions(+), 78 deletions(-) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 48e081760c..a468a43dd4 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2863,17 +2863,18 @@ void EditorNode::_dialog_action(String p_file) { case LAYOUT_DELETE: { Ref config; config.instantiate(); - Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); + Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); if (err != OK || !config->has_section(p_file)) { show_warning(TTR("Layout name not found!")); return; } - // Erase key values. - Vector keys = config->get_section_keys(p_file); - for (const String &key : keys) { - config->set_value(p_file, key, Variant()); + for (const String §ion : config->get_sections()) { + // Erase sections related to the layout. + if (section == p_file || section.begins_with(p_file + "/")) { + config->erase_section(section); + } } config->save(EditorSettings::get_singleton()->get_editor_layouts_config()); @@ -6350,8 +6351,8 @@ void EditorNode::_load_editor_layout() { favorites->set_collapsed(false); } - if (overridden_default_layout >= 0) { - _layout_menu_option(overridden_default_layout); + if (overridden_default_layout) { + _layout_menu_option(LAYOUT_DEFAULT); } else { ep.step(TTR("Loading docks..."), 1, true); // Initialize some default values. @@ -6643,36 +6644,32 @@ void EditorNode::cleanup() { void EditorNode::_update_layouts_menu() { editor_layouts->clear(); - overridden_default_layout = -1; + overridden_default_layout = false; editor_layouts->reset_size(); editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTRC("Save Layout...")), LAYOUT_SAVE); editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTRC("Delete Layout...")), LAYOUT_DELETE); editor_layouts->add_separator(); - editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", TTRC("Default")), LAYOUT_DEFAULT); Ref config; config.instantiate(); Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); + if (err == OK && config->has_section("Default")) { + overridden_default_layout = true; + } + + editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", overridden_default_layout ? TTRC("Default (Overridden)") : TTRC("Default")), LAYOUT_DEFAULT); + if (err != OK) { return; // No config. } Vector layouts = config->get_sections(); - const String default_layout_name = TTR("Default"); - for (const String &layout : layouts) { - if (layout.contains_char('/')) { - continue; + if (layout != "Default" && !layout.contains_char('/')) { + editor_layouts->add_item(layout); + editor_layouts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED); } - - if (layout == default_layout_name) { - editor_layouts->remove_item(editor_layouts->get_item_index(LAYOUT_DEFAULT)); - overridden_default_layout = editor_layouts->get_item_count(); - } - - editor_layouts->add_item(layout); - editor_layouts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED); } } @@ -6680,32 +6677,40 @@ void EditorNode::_layout_menu_option(int p_id) { switch (p_id) { case LAYOUT_SAVE: { current_menu_option = p_id; - layout_dialog->set_title(TTR("Save Layout")); - layout_dialog->set_ok_button_text(TTR("Save")); - layout_dialog->set_name_line_enabled(true); + layout_dialog->set_save_mode_enabled(true); layout_dialog->popup_centered(); } break; + case LAYOUT_DELETE: { current_menu_option = p_id; - layout_dialog->set_title(TTR("Delete Layout")); - layout_dialog->set_ok_button_text(TTR("Delete")); - layout_dialog->set_name_line_enabled(false); + layout_dialog->set_save_mode_enabled(false); layout_dialog->popup_centered(); } break; + case LAYOUT_DEFAULT: { + // Check if the default layout was overridden, and if so, select that instead. + Ref config; + config.instantiate(); + Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); + if (err == OK && config->has_section("Default")) { + editor_dock_manager->load_docks_from_config(config, "Default"); + _save_editor_layout(); + + return; + } + editor_dock_manager->load_docks_from_config(default_layout, "docks"); _save_editor_layout(); } break; + default: { Ref config; config.instantiate(); Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); - if (err != OK) { - return; // No config. + if (err == OK) { + editor_dock_manager->load_docks_from_config(config, editor_layouts->get_item_text(p_id)); + _save_editor_layout(); } - - editor_dock_manager->load_docks_from_config(config, editor_layouts->get_item_text(p_id)); - _save_editor_layout(); } } } diff --git a/editor/editor_node.h b/editor/editor_node.h index dcd50a5499..9d4f4eb125 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -387,7 +387,7 @@ private: AcceptDialog *warning = nullptr; EditorPlugin *plugin_to_save = nullptr; - int overridden_default_layout = -1; + bool overridden_default_layout = false; Ref default_layout; PopupMenu *editor_layouts = nullptr; EditorLayoutsDialog *layout_dialog = nullptr; diff --git a/editor/settings/editor_layouts_dialog.cpp b/editor/settings/editor_layouts_dialog.cpp index b8d30f00fa..83fae0e08b 100644 --- a/editor/settings/editor_layouts_dialog.cpp +++ b/editor/settings/editor_layouts_dialog.cpp @@ -32,41 +32,64 @@ #include "core/io/config_file.h" #include "core/object/callable_mp.h" +#include "editor/gui/editor_validation_panel.h" #include "editor/settings/editor_settings.h" #include "editor/themes/editor_scale.h" #include "scene/gui/item_list.h" #include "scene/gui/line_edit.h" #include "scene/gui/margin_container.h" -void EditorLayoutsDialog::_line_gui_input(const Ref &p_event) { - Ref k = p_event; +void EditorLayoutsDialog::_validate_name() { + const String layout_name = name->get_text().strip_edges(); - if (k.is_valid()) { - if (k->is_action_pressed(SNAME("ui_text_submit"), false, true)) { - if (get_hide_on_ok()) { - hide(); - } - ok_pressed(); - set_input_as_handled(); - } else if (k->is_action_pressed(SNAME("ui_cancel"), false, true)) { - hide(); - set_input_as_handled(); + if (!layout_names->is_anything_selected()) { + String error; + + if (layout_name.is_empty()) { + error = TTRC("Layout name can't be empty."); + } else if (layout_name.contains_char('/') || layout_name.contains_char('\\')) { + error = TTRC("Layout name contains invalid characters: \"/\" or \"\\\"."); + } + + if (error != "") { + validation->set_message(0, error, EditorValidationPanel::MSG_ERROR); + return; } } -} -void EditorLayoutsDialog::_update_ok_disable_state() { if (layout_names->is_anything_selected()) { - get_ok_button()->set_disabled(false); - } else { - get_ok_button()->set_disabled(!name->is_visible() || name->get_text().strip_edges().is_empty()); + validation->set_message(0, TTRC("Selected layout will be overridden."), EditorValidationPanel::MSG_OK); + return; } + + bool name_in_use = false; + for (int i = 0; i < layout_names->get_item_count(); i++) { + if (layout_names->get_item_metadata(i) == layout_name) { + name_in_use = true; + break; + } + } + validation->set_message(0, name_in_use ? TTRC("Layout already exists and will be overridden.") : TTRC("Layout name is valid."), EditorValidationPanel::MSG_OK); } void EditorLayoutsDialog::_deselect_layout_names() { - // The deselect method does not emit any signal, therefore we need update the disable state as well. + // The deselect method does not emit any signal, therefore we need update the validation state as well. layout_names->deselect_all(); - _update_ok_disable_state(); + validation->update(); +} + +void EditorLayoutsDialog::_multi_selected() { + get_ok_button()->set_disabled(!layout_names->is_anything_selected()); +} + +void EditorLayoutsDialog::_item_activated() { + if (layout_names->is_anything_selected()) { + for (const int item : layout_names->get_selected_items()) { + emit_signal(SNAME("name_confirmed"), layout_names->get_item_metadata(item)); + } + + hide(); + } } void EditorLayoutsDialog::_bind_methods() { @@ -75,9 +98,8 @@ void EditorLayoutsDialog::_bind_methods() { void EditorLayoutsDialog::ok_pressed() { if (layout_names->is_anything_selected()) { - Vector const selected_items = layout_names->get_selected_items(); - for (int i = 0; i < selected_items.size(); ++i) { - emit_signal(SNAME("name_confirmed"), layout_names->get_item_text(selected_items[i])); + for (const int item : layout_names->get_selected_items()) { + emit_signal(SNAME("name_confirmed"), layout_names->get_item_metadata(item)); } } else if (name->is_visible() && !name->get_text().strip_edges().is_empty()) { emit_signal(SNAME("name_confirmed"), name->get_text().strip_edges()); @@ -89,24 +111,32 @@ void EditorLayoutsDialog::_post_popup() { layout_names->clear(); name->clear(); - Ref config; - config.instantiate(); - Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); - if (err != OK) { - return; - } - - Vector layouts = config->get_sections(); - - for (const String &E : layouts) { - if (!E.contains_char('/')) { - layout_names->add_item(E); - } - } - if (name->is_visible()) { + if (save_mode) { + layout_names->add_item(TTR("Default")); + layout_names->set_item_metadata(0, "Default"); name->grab_focus(); } else { layout_names->grab_focus(true); + get_ok_button()->set_disabled(true); + } + + Ref config; + config.instantiate(); + Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); + if (err == OK) { + Vector layouts = config->get_sections(); + + if (!save_mode && layouts.has("Default")) { + layout_names->add_item(TTR("Default (Restore)")); + layout_names->set_item_metadata(0, "Default"); + } + + for (const String &E : layouts) { + if (E != "Default" && !E.contains_char('/')) { + layout_names->add_item(E); + layout_names->set_item_metadata(-1, E); + } + } } } @@ -114,12 +144,19 @@ EditorLayoutsDialog::EditorLayoutsDialog() { makevb = memnew(VBoxContainer); add_child(makevb); + validation = memnew(EditorValidationPanel); + validation->add_line(0); + validation->set_update_callback(callable_mp(this, &EditorLayoutsDialog::_validate_name)); + validation->set_accept_button(get_ok_button()); + validation->set_v_size_flags(0); + layout_names = memnew(ItemList); layout_names->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); - layout_names->set_select_mode(ItemList::SELECT_MULTI); layout_names->set_allow_rmb_select(true); layout_names->set_scroll_hint_mode(ItemList::SCROLL_HINT_MODE_BOTH); - layout_names->connect("multi_selected", callable_mp(this, &EditorLayoutsDialog::_update_ok_disable_state).unbind(2)); + layout_names->connect(SceneStringName(item_selected), callable_mp(validation, &EditorValidationPanel::update).unbind(1)); + layout_names->connect("multi_selected", callable_mp(this, &EditorLayoutsDialog::_multi_selected).unbind(2)); // For deletion mode. + layout_names->connect("item_activated", callable_mp(this, &EditorLayoutsDialog::_item_activated).unbind(1)); MarginContainer *mc = makevb->add_margin_child(TTRC("Select Existing Layout:"), layout_names); mc->set_custom_minimum_size(Size2(300 * EDSCALE, 50 * EDSCALE)); @@ -130,11 +167,22 @@ EditorLayoutsDialog::EditorLayoutsDialog() { makevb->add_child(name); name->set_placeholder(TTRC("Or enter new layout name.")); name->set_accessibility_name(TTRC("New layout name")); - name->connect(SceneStringName(gui_input), callable_mp(this, &EditorLayoutsDialog::_line_gui_input)); + register_text_enter(name); name->connect(SceneStringName(focus_entered), callable_mp(this, &EditorLayoutsDialog::_deselect_layout_names)); - name->connect(SceneStringName(text_changed), callable_mp(this, &EditorLayoutsDialog::_update_ok_disable_state).unbind(1)); + name->connect(SceneStringName(text_changed), callable_mp(validation, &EditorValidationPanel::update).unbind(1)); + + makevb->add_child(validation); + + set_save_mode_enabled(save_mode); } -void EditorLayoutsDialog::set_name_line_enabled(bool p_enabled) { +void EditorLayoutsDialog::set_save_mode_enabled(bool p_enabled) { + save_mode = p_enabled; + + set_title(p_enabled ? TTRC("Save Layout") : TTRC("Delete Layout")); + set_ok_button_text(p_enabled ? TTRC("Save") : TTRC("Delete")); + + layout_names->set_select_mode(p_enabled ? ItemList::SELECT_SINGLE : ItemList::SELECT_MULTI); name->set_visible(p_enabled); + validation->set_visible(p_enabled); } diff --git a/editor/settings/editor_layouts_dialog.h b/editor/settings/editor_layouts_dialog.h index 761f15a428..1e45c7c02f 100644 --- a/editor/settings/editor_layouts_dialog.h +++ b/editor/settings/editor_layouts_dialog.h @@ -32,19 +32,24 @@ #include "scene/gui/dialogs.h" +class EditorValidationPanel; class LineEdit; class ItemList; class EditorLayoutsDialog : public ConfirmationDialog { GDCLASS(EditorLayoutsDialog, ConfirmationDialog); + bool save_mode = true; + LineEdit *name = nullptr; + EditorValidationPanel *validation = nullptr; ItemList *layout_names = nullptr; VBoxContainer *makevb = nullptr; - void _line_gui_input(const Ref &p_event); - void _update_ok_disable_state(); void _deselect_layout_names(); + void _validate_name(); + void _multi_selected(); + void _item_activated(); protected: static void _bind_methods(); @@ -54,5 +59,5 @@ protected: public: EditorLayoutsDialog(); - void set_name_line_enabled(bool p_enabled); + void set_save_mode_enabled(bool p_enabled); };