diff --git a/editor/directory_create_dialog.cpp b/editor/directory_create_dialog.cpp index df860bab2c0..fed7cb82c91 100644 --- a/editor/directory_create_dialog.cpp +++ b/editor/directory_create_dialog.cpp @@ -33,10 +33,10 @@ #include "core/io/dir_access.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/gui/editor_validation_panel.h" #include "scene/gui/box_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" -#include "scene/gui/panel_container.h" static String sanitize_input(const String &p_path) { String path = p_path.strip_edges(); @@ -73,24 +73,17 @@ String DirectoryCreateDialog::_validate_path(const String &p_path) const { return String(); } -void DirectoryCreateDialog::_on_dir_path_changed(const String &p_text) { - const String path = sanitize_input(p_text); +void DirectoryCreateDialog::_on_dir_path_changed() { + const String path = sanitize_input(dir_path->get_text()); const String error = _validate_path(path); if (error.is_empty()) { - status_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); - if (path.contains("/")) { - status_label->set_text(TTR("Using slashes in folder names will create subfolders recursively.")); - } else { - status_label->set_text(TTR("Folder name is valid.")); + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK); } } else { - status_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - status_label->set_text(error); + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR); } - - get_ok_button()->set_disabled(!error.is_empty()); } void DirectoryCreateDialog::ok_pressed() { @@ -127,21 +120,13 @@ void DirectoryCreateDialog::config(const String &p_base_dir) { label->set_text(vformat(TTR("Create new folder in %s:"), base_dir)); dir_path->set_text("new folder"); dir_path->select_all(); - _on_dir_path_changed(dir_path->get_text()); + validation_panel->update(); } void DirectoryCreateDialog::_bind_methods() { ADD_SIGNAL(MethodInfo("dir_created")); } -void DirectoryCreateDialog::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); - } break; - } -} - DirectoryCreateDialog::DirectoryCreateDialog() { set_title(TTR("Create Folder")); set_min_size(Size2i(480, 0) * EDSCALE); @@ -154,7 +139,6 @@ DirectoryCreateDialog::DirectoryCreateDialog() { vb->add_child(label); dir_path = memnew(LineEdit); - dir_path->connect("text_changed", callable_mp(this, &DirectoryCreateDialog::_on_dir_path_changed)); vb->add_child(dir_path); register_text_enter(dir_path); @@ -162,11 +146,11 @@ DirectoryCreateDialog::DirectoryCreateDialog() { spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); vb->add_child(spacing); - status_panel = memnew(PanelContainer); - status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); - vb->add_child(status_panel); + validation_panel = memnew(EditorValidationPanel); + vb->add_child(validation_panel); + validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Folder name is valid.")); + validation_panel->set_update_callback(callable_mp(this, &DirectoryCreateDialog::_on_dir_path_changed)); + validation_panel->set_accept_button(get_ok_button()); - status_label = memnew(Label); - status_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - status_panel->add_child(status_label); + dir_path->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); } diff --git a/editor/directory_create_dialog.h b/editor/directory_create_dialog.h index e2601181da8..82e2e98ae53 100644 --- a/editor/directory_create_dialog.h +++ b/editor/directory_create_dialog.h @@ -33,9 +33,9 @@ #include "scene/gui/dialogs.h" +class EditorValidationPanel; class Label; class LineEdit; -class PanelContainer; class DirectoryCreateDialog : public ConfirmationDialog { GDCLASS(DirectoryCreateDialog, ConfirmationDialog); @@ -44,17 +44,13 @@ class DirectoryCreateDialog : public ConfirmationDialog { Label *label = nullptr; LineEdit *dir_path = nullptr; - - PanelContainer *status_panel = nullptr; - Label *status_label = nullptr; + EditorValidationPanel *validation_panel = nullptr; String _validate_path(const String &p_path) const; - - void _on_dir_path_changed(const String &p_text); + void _on_dir_path_changed(); protected: static void _bind_methods(); - void _notification(int p_what); virtual void ok_pressed() override; virtual void _post_popup() override; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index d95b1de3658..7ac812101aa 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -38,6 +38,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_validation_panel.h" #include "editor/inspector_dock.h" #include "editor/plugins/script_editor_plugin.h" #include "multi_node_edit.h" @@ -3927,12 +3928,6 @@ void EditorInspector::_notification(int p_what) { } } break; - case NOTIFICATION_THEME_CHANGED: { - if (add_meta_error_panel) { - add_meta_error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); - } - } break; - case NOTIFICATION_PREDELETE: { edit(nullptr); //just in case } break; @@ -4083,27 +4078,17 @@ void EditorInspector::_add_meta_confirm() { undo_redo->commit_action(); } -void EditorInspector::_check_meta_name(const String &p_name) { - String error; +void EditorInspector::_check_meta_name() { + const String meta_name = add_meta_name->get_text(); - if (p_name == "") { - error = TTR("Metadata name can't be empty."); - } else if (!p_name.is_valid_identifier()) { - error = TTR("Metadata name must be a valid identifier."); - } else if (object->has_meta(p_name)) { - error = vformat(TTR("Metadata with name \"%s\" already exists."), p_name); - } else if (p_name[0] == '_') { - error = TTR("Names starting with _ are reserved for editor-only metadata."); - } - - if (error != "") { - add_meta_error->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - add_meta_error->set_text(error); - add_meta_dialog->get_ok_button()->set_disabled(true); - } else { - add_meta_error->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); - add_meta_error->set_text(TTR("Metadata name is valid.")); - add_meta_dialog->get_ok_button()->set_disabled(false); + if (meta_name.is_empty()) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR); + } else if (!meta_name.is_valid_identifier()) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR); + } else if (object->has_meta(meta_name)) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR); + } else if (meta_name[0] == '_') { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata."), EditorValidationPanel::MSG_ERROR); } } @@ -4143,16 +4128,13 @@ void EditorInspector::_show_add_meta_dialog() { add_meta_dialog->register_text_enter(add_meta_name); add_meta_dialog->connect("confirmed", callable_mp(this, &EditorInspector::_add_meta_confirm)); - add_meta_error_panel = memnew(PanelContainer); - vbc->add_child(add_meta_error_panel); - if (is_inside_tree()) { - add_meta_error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); - } + validation_panel = memnew(EditorValidationPanel); + vbc->add_child(validation_panel); + validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid.")); + validation_panel->set_update_callback(callable_mp(this, &EditorInspector::_check_meta_name)); + validation_panel->set_accept_button(add_meta_dialog->get_ok_button()); - add_meta_error = memnew(Label); - add_meta_error_panel->add_child(add_meta_error); - - add_meta_name->connect("text_changed", callable_mp(this, &EditorInspector::_check_meta_name)); + add_meta_name->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); } Node *node = Object::cast_to(object); @@ -4164,9 +4146,9 @@ void EditorInspector::_show_add_meta_dialog() { } add_meta_dialog->popup_centered(); - add_meta_name->set_text(""); - _check_meta_name(""); add_meta_name->grab_focus(); + add_meta_name->set_text(""); + validation_panel->update(); } void EditorInspector::_bind_methods() { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 9a4c4f7f992..ed0d0ec373b 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -39,6 +39,7 @@ class AcceptDialog; class Button; class ConfirmationDialog; class EditorInspector; +class EditorValidationPanel; class LineEdit; class OptionButton; class PanelContainer; @@ -543,12 +544,11 @@ class EditorInspector : public ScrollContainer { ConfirmationDialog *add_meta_dialog = nullptr; LineEdit *add_meta_name = nullptr; OptionButton *add_meta_type = nullptr; - PanelContainer *add_meta_error_panel = nullptr; - Label *add_meta_error = nullptr; + EditorValidationPanel *validation_panel = nullptr; void _add_meta_confirm(); void _show_add_meta_dialog(); - void _check_meta_name(const String &p_name); + void _check_meta_name(); protected: static void _bind_methods(); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 80cefecee29..628976ac57a 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1280,6 +1280,7 @@ Ref create_editor_theme(const Ref p_theme) { } theme->set_stylebox("panel", "Tree", style_tree_bg); + theme->set_stylebox("panel", "EditorValidationPanel", style_tree_bg); // Tree theme->set_icon("checked", "Tree", theme->get_icon(SNAME("GuiChecked"), SNAME("EditorIcons"))); diff --git a/editor/gui/editor_validation_panel.cpp b/editor/gui/editor_validation_panel.cpp new file mode 100644 index 00000000000..af15010b780 --- /dev/null +++ b/editor/gui/editor_validation_panel.cpp @@ -0,0 +1,134 @@ +/**************************************************************************/ +/* editor_validation_panel.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 "editor_validation_panel.h" + +#include "editor/editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" + +void EditorValidationPanel::_update() { + for (const KeyValue &E : valid_messages) { + set_message(E.key, E.value, MSG_OK); + } + + valid = true; + update_callback.callv(Array()); + + if (accept_button) { + accept_button->set_disabled(!valid); + } + pending_update = false; +} + +void EditorValidationPanel::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + theme_cache.valid_color = get_theme_color(SNAME("success_color"), SNAME("Editor")); + theme_cache.warning_color = get_theme_color(SNAME("warning_color"), SNAME("Editor")); + theme_cache.error_color = get_theme_color(SNAME("error_color"), SNAME("Editor")); + } break; + } +} + +void EditorValidationPanel::add_line(int p_id, const String &p_valid_message) { + ERR_FAIL_COND(valid_messages.has(p_id)); + + Label *label = memnew(Label); + message_container->add_child(label); + label->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); + label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + + valid_messages[p_id] = p_valid_message; + labels[p_id] = label; +} + +void EditorValidationPanel::set_accept_button(Button *p_button) { + accept_button = p_button; +} + +void EditorValidationPanel::set_update_callback(const Callable &p_callback) { + update_callback = p_callback; +} + +void EditorValidationPanel::update() { + ERR_FAIL_COND(update_callback.is_null()); + + if (pending_update) { + return; + } + pending_update = true; + callable_mp(this, &EditorValidationPanel::_update).call_deferred(); +} + +void EditorValidationPanel::set_message(int p_id, const String &p_text, MessageType p_type, bool p_auto_prefix) { + ERR_FAIL_COND(!valid_messages.has(p_id)); + + Label *label = labels[p_id]; + if (p_text.is_empty()) { + label->hide(); + return; + } + label->show(); + + if (p_auto_prefix) { + label->set_text(String(U"• ") + p_text); + } else { + label->set_text(p_text); + } + + switch (p_type) { + case MSG_OK: + label->add_theme_color_override(SNAME("font_color"), theme_cache.valid_color); + break; + case MSG_WARNING: + label->add_theme_color_override(SNAME("font_color"), theme_cache.warning_color); + break; + case MSG_ERROR: + label->add_theme_color_override(SNAME("font_color"), theme_cache.error_color); + valid = false; + break; + case MSG_INFO: + label->remove_theme_color_override(SNAME("font_color")); + break; + } +} + +bool EditorValidationPanel::is_valid() const { + return valid; +} + +EditorValidationPanel::EditorValidationPanel() { + set_v_size_flags(SIZE_EXPAND_FILL); + + message_container = memnew(VBoxContainer); + add_child(message_container); +} diff --git a/editor/gui/editor_validation_panel.h b/editor/gui/editor_validation_panel.h new file mode 100644 index 00000000000..c371795e150 --- /dev/null +++ b/editor/gui/editor_validation_panel.h @@ -0,0 +1,88 @@ +/**************************************************************************/ +/* editor_validation_panel.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 EDITOR_VALIDATION_PANEL_H +#define EDITOR_VALIDATION_PANEL_H + +#include "scene/gui/panel_container.h" + +class Button; +class Label; +class VBoxContainer; + +class EditorValidationPanel : public PanelContainer { + GDCLASS(EditorValidationPanel, PanelContainer); + +public: + enum MessageType { + MSG_OK, + MSG_WARNING, + MSG_ERROR, + MSG_INFO, + }; + + static const int MSG_ID_DEFAULT = 0; // Avoids hard-coding ID in dialogs with single-line validation. + +private: + VBoxContainer *message_container = nullptr; + + HashMap valid_messages; + HashMap labels; + + bool valid = false; + bool pending_update = false; + + struct ThemeCache { + Color valid_color; + Color warning_color; + Color error_color; + } theme_cache; + + void _update(); + + Callable update_callback; + Button *accept_button = nullptr; + +protected: + void _notification(int p_what); + +public: + void add_line(int p_id, const String &p_valid_message = ""); + void set_accept_button(Button *p_button); + void set_update_callback(const Callable &p_callback); + + void update(); + void set_message(int p_id, const String &p_text, MessageType p_type, bool p_auto_prefix = true); + bool is_valid() const; + + EditorValidationPanel(); +}; + +#endif // EDITOR_VALIDATION_PANEL_H diff --git a/editor/scene_create_dialog.cpp b/editor/scene_create_dialog.cpp index 986f6bb87a6..8f562671236 100644 --- a/editor/scene_create_dialog.cpp +++ b/editor/scene_create_dialog.cpp @@ -34,6 +34,7 @@ #include "editor/create_dialog.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/gui/editor_validation_panel.h" #include "scene/2d/node_2d.h" #include "scene/3d/node_3d.h" #include "scene/gui/box_container.h" @@ -41,7 +42,6 @@ #include "scene/gui/grid_container.h" #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" -#include "scene/gui/panel_container.h" #include "scene/resources/packed_scene.h" void SceneCreateDialog::_notification(int p_what) { @@ -53,7 +53,6 @@ void SceneCreateDialog::_notification(int p_what) { node_type_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons"))); node_type_gui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons"))); node_type_other->add_theme_icon_override(SNAME("icon"), get_theme_icon(SNAME("Node"), SNAME("EditorIcons"))); - status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } break; } } @@ -63,7 +62,7 @@ void SceneCreateDialog::config(const String &p_dir) { root_name_edit->set_text(""); scene_name_edit->set_text(""); scene_name_edit->call_deferred(SNAME("grab_focus")); - update_dialog(); + validation_panel->update(); } void SceneCreateDialog::accept_create() { @@ -82,40 +81,35 @@ void SceneCreateDialog::browse_types() { void SceneCreateDialog::on_type_picked() { other_type_display->set_text(select_node_dialog->get_selected_type().get_slice(" ", 0)); if (node_type_other->is_pressed()) { - update_dialog(); + validation_panel->update(); } else { - node_type_other->set_pressed(true); // Calls update_dialog() via group. + node_type_other->set_pressed(true); // Calls validation_panel->update() via group. } } void SceneCreateDialog::update_dialog() { scene_name = scene_name_edit->get_text().strip_edges(); - update_error(file_error_label, MSG_OK, TTR("Scene name is valid.")); - bool is_valid = true; if (scene_name.is_empty()) { - update_error(file_error_label, MSG_ERROR, TTR("Scene name is empty.")); - is_valid = false; + validation_panel->set_message(MSG_ID_PATH, TTR("Scene name is empty."), EditorValidationPanel::MSG_ERROR); } - if (is_valid) { + if (validation_panel->is_valid()) { if (!scene_name.ends_with(".")) { scene_name += "."; } scene_name += scene_extension_picker->get_selected_metadata().operator String(); } - if (is_valid && !scene_name.is_valid_filename()) { - update_error(file_error_label, MSG_ERROR, TTR("File name invalid.")); - is_valid = false; + if (validation_panel->is_valid() && !scene_name.is_valid_filename()) { + validation_panel->set_message(MSG_ID_PATH, TTR("File name invalid."), EditorValidationPanel::MSG_ERROR); } - if (is_valid) { + if (validation_panel->is_valid()) { scene_name = directory.path_join(scene_name); Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (da->file_exists(scene_name)) { - update_error(file_error_label, MSG_ERROR, TTR("File already exists.")); - is_valid = false; + validation_panel->set_message(MSG_ID_PATH, TTR("File already exists."), EditorValidationPanel::MSG_ERROR); } } @@ -126,8 +120,6 @@ void SceneCreateDialog::update_dialog() { node_type_other->set_icon(nullptr); } - update_error(node_error_label, MSG_OK, TTR("Root node valid.")); - root_name = root_name_edit->get_text().strip_edges(); if (root_name.is_empty()) { root_name = scene_name_edit->get_text().strip_edges(); @@ -135,39 +127,16 @@ void SceneCreateDialog::update_dialog() { if (root_name.is_empty()) { root_name_edit->set_placeholder(TTR("Leave empty to derive from scene name")); } else { - // Respect the desired root node casing from ProjectSettings and ensure it's a valid node name. - String adjusted_root_name = Node::adjust_name_casing(root_name); - root_name = adjusted_root_name.validate_node_name(); - - bool has_invalid_characters = root_name != adjusted_root_name; - if (has_invalid_characters) { - update_error(node_error_label, MSG_WARNING, TTR("Invalid root node name characters have been replaced.")); - } - - root_name_edit->set_placeholder(root_name); + // Respect the desired root node casing from ProjectSettings. + root_name = Node::adjust_name_casing(root_name); + root_name_edit->set_placeholder(root_name.validate_node_name()); } } - if (root_name.is_empty() || root_name.validate_node_name() != root_name) { - update_error(node_error_label, MSG_ERROR, TTR("Invalid root node name.")); - is_valid = false; - } - - get_ok_button()->set_disabled(!is_valid); -} - -void SceneCreateDialog::update_error(Label *p_label, MsgType p_type, const String &p_msg) { - p_label->set_text(String::utf8("• ") + p_msg); - switch (p_type) { - case MSG_OK: - p_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); - break; - case MSG_ERROR: - p_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - break; - case MSG_WARNING: - p_label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - break; + if (root_name.is_empty()) { + validation_panel->set_message(MSG_ID_ROOT, TTR("Invalid root node name."), EditorValidationPanel::MSG_ERROR); + } else if (root_name != root_name.validate_node_name()) { + validation_panel->set_message(MSG_ID_ROOT, TTR("Invalid root node name characters have been replaced."), EditorValidationPanel::MSG_WARNING); } } @@ -268,8 +237,6 @@ SceneCreateDialog::SceneCreateDialog() { select_node_button = memnew(Button); hb->add_child(select_node_button); select_node_button->connect("pressed", callable_mp(this, &SceneCreateDialog::browse_types)); - - node_type_group->connect("pressed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); } { @@ -282,7 +249,6 @@ SceneCreateDialog::SceneCreateDialog() { scene_name_edit = memnew(LineEdit); hb->add_child(scene_name_edit); scene_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); - scene_name_edit->connect("text_changed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); scene_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1)); List extensions; @@ -305,7 +271,6 @@ SceneCreateDialog::SceneCreateDialog() { gc->add_child(root_name_edit); root_name_edit->set_tooltip_text(TTR("When empty, the root node name is derived from the scene name based on the \"editor/naming/node_name_casing\" project setting.")); root_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); - root_name_edit->connect("text_changed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); root_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1)); } @@ -313,19 +278,16 @@ SceneCreateDialog::SceneCreateDialog() { main_vb->add_child(spacing); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); - status_panel = memnew(PanelContainer); - main_vb->add_child(status_panel); - status_panel->set_h_size_flags(Control::SIZE_FILL); - status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); + validation_panel = memnew(EditorValidationPanel); + main_vb->add_child(validation_panel); + validation_panel->add_line(MSG_ID_PATH, TTR("Scene name is valid.")); + validation_panel->add_line(MSG_ID_ROOT, TTR("Root node valid.")); + validation_panel->set_update_callback(callable_mp(this, &SceneCreateDialog::update_dialog)); + validation_panel->set_accept_button(get_ok_button()); - VBoxContainer *status_vb = memnew(VBoxContainer); - status_panel->add_child(status_vb); - - file_error_label = memnew(Label); - status_vb->add_child(file_error_label); - - node_error_label = memnew(Label); - status_vb->add_child(node_error_label); + node_type_group->connect("pressed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + scene_name_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + root_name_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); set_title(TTR("Create New Scene")); set_min_size(Size2i(400 * EDSCALE, 0)); diff --git a/editor/scene_create_dialog.h b/editor/scene_create_dialog.h index c6f40b928e8..c7a4a098504 100644 --- a/editor/scene_create_dialog.h +++ b/editor/scene_create_dialog.h @@ -37,18 +37,17 @@ class ButtonGroup; class CheckBox; class CreateDialog; class EditorFileDialog; +class EditorValidationPanel; class Label; class LineEdit; class OptionButton; -class PanelContainer; class SceneCreateDialog : public ConfirmationDialog { GDCLASS(SceneCreateDialog, ConfirmationDialog); - enum MsgType { - MSG_OK, - MSG_ERROR, - MSG_WARNING, + enum { + MSG_ID_PATH, + MSG_ID_ROOT, }; const StringName type_meta = StringName("type"); @@ -80,15 +79,12 @@ private: OptionButton *scene_extension_picker = nullptr; LineEdit *root_name_edit = nullptr; - PanelContainer *status_panel = nullptr; - Label *file_error_label = nullptr; - Label *node_error_label = nullptr; + EditorValidationPanel *validation_panel = nullptr; void accept_create(); void browse_types(); void on_type_picked(); void update_dialog(); - void update_error(Label *p_label, MsgType p_type, const String &p_msg); protected: void _notification(int p_what); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 058f4c8d914..daac7555295 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -41,6 +41,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/gui/editor_validation_panel.h" static String _get_parent_class_of_script(String p_path) { if (!ResourceLoader::exists(p_path, "Script")) { @@ -136,7 +137,6 @@ void ScriptCreateDialog::_notification(int p_what) { path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); parent_browse_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); parent_search_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); - status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } break; } } @@ -295,13 +295,7 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must } // Let ScriptLanguage do custom validation. - String path_error = ScriptServer::get_language(language_menu->get_selected())->validate_path(p); - if (!path_error.is_empty()) { - return path_error; - } - - // All checks passed. - return ""; + return ScriptServer::get_language(language_menu->get_selected())->validate_path(p); } String ScriptCreateDialog::_get_class_name() const { @@ -314,12 +308,12 @@ String ScriptCreateDialog::_get_class_name() const { void ScriptCreateDialog::_class_name_changed(const String &p_name) { is_class_name_valid = _validate_class(class_name->get_text()); - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_parent_name_changed(const String &p_parent) { is_parent_name_valid = _validate_parent(parent_name->get_text()); - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_template_changed(int p_template) { @@ -347,6 +341,7 @@ void ScriptCreateDialog::_template_changed(int p_template) { } } } + // Update template label information. String template_info = U"• "; template_info += TTR("Template:"); @@ -354,8 +349,7 @@ void ScriptCreateDialog::_template_changed(int p_template) { if (!sinfo.description.is_empty()) { template_info += " - " + sinfo.description; } - template_info_label->set_text(template_info); - template_info_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); + validation_panel->set_message(MSG_ID_TEMPLATE, template_info, EditorValidationPanel::MSG_INFO, false); } void ScriptCreateDialog::ok_pressed() { @@ -367,7 +361,7 @@ void ScriptCreateDialog::ok_pressed() { EditorSettings::get_singleton()->save(); is_new_script_created = true; - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_create_new() { @@ -471,7 +465,7 @@ void ScriptCreateDialog::_language_changed(int l) { EditorSettings::get_singleton()->set_project_metadata("script_setup", "last_selected_language", language_menu->get_item_text(language_menu->get_selected())); _parent_name_changed(parent_name->get_text()); - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_built_in_pressed() { @@ -482,13 +476,13 @@ void ScriptCreateDialog::_built_in_pressed() { is_built_in = false; _path_changed(file_path->get_text()); } - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_use_template_pressed() { is_using_templates = use_templates->is_pressed(); EditorSettings::get_singleton()->set_meta("script_setup_use_script_templates", is_using_templates); - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_browse_path(bool browse_parent, bool p_save) { @@ -555,10 +549,9 @@ void ScriptCreateDialog::_path_changed(const String &p_path) { is_path_valid = false; is_new_script_created = true; - String path_error = _validate_path(p_path, false); + path_error = _validate_path(p_path, false); if (!path_error.is_empty()) { - _msg_path_valid(false, path_error); - _update_dialog(); + validation_panel->update(); return; } @@ -567,32 +560,15 @@ void ScriptCreateDialog::_path_changed(const String &p_path) { String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges()); if (da->file_exists(p)) { is_new_script_created = false; - _msg_path_valid(true, TTR("File exists, it will be reused.")); } is_path_valid = true; - _update_dialog(); + validation_panel->update(); } void ScriptCreateDialog::_path_submitted(const String &p_path) { - ok_pressed(); -} - -void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) { - error_label->set_text(String::utf8("• ") + p_msg); - if (valid) { - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); - } else { - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } -} - -void ScriptCreateDialog::_msg_path_valid(bool valid, const String &p_msg) { - path_error_label->set_text(String::utf8("• ") + p_msg); - if (valid) { - path_error_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); - } else { - path_error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + if (!get_ok_button()->is_disabled()) { + ok_pressed(); } } @@ -688,25 +664,25 @@ void ScriptCreateDialog::_update_template_menu() { void ScriptCreateDialog::_update_dialog() { // "Add Script Dialog" GUI logic and script checks. _update_template_menu(); - bool script_ok = true; // Is script path/name valid (order from top to bottom)? if (!is_built_in && !is_path_valid) { - _msg_script_valid(false, TTR("Invalid path.")); - script_ok = false; + validation_panel->set_message(MSG_ID_SCRIPT, TTR("Invalid path."), EditorValidationPanel::MSG_ERROR); } if (has_named_classes && (is_new_script_created && !is_class_name_valid)) { - _msg_script_valid(false, TTR("Invalid class name.")); - script_ok = false; + validation_panel->set_message(MSG_ID_SCRIPT, TTR("Invalid class name."), EditorValidationPanel::MSG_ERROR); } if (!is_parent_name_valid && is_new_script_created) { - _msg_script_valid(false, TTR("Invalid inherited parent name or path.")); - script_ok = false; + validation_panel->set_message(MSG_ID_SCRIPT, TTR("Invalid inherited parent name or path."), EditorValidationPanel::MSG_ERROR); } - if (script_ok) { - _msg_script_valid(true, TTR("Script path/name is valid.")); + if (validation_panel->is_valid() && !is_new_script_created) { + validation_panel->set_message(MSG_ID_SCRIPT, TTR("File exists, it will be reused."), EditorValidationPanel::MSG_OK); + } + + if (!path_error.is_empty()) { + validation_panel->set_message(MSG_ID_PATH, path_error, EditorValidationPanel::MSG_ERROR); } // Does script have named classes? @@ -752,7 +728,11 @@ void ScriptCreateDialog::_update_dialog() { // Is Script created or loaded from existing file? - builtin_warning_label->set_visible(is_built_in); + if (is_built_in) { + validation_panel->set_message(MSG_ID_BUILT_IN, TTR("Note: Built-in scripts have some limitations and can't be edited using an external editor."), EditorValidationPanel::MSG_INFO, false); + } else if (_get_class_name() == parent_name->get_text()) { + validation_panel->set_message(MSG_ID_BUILT_IN, TTR("Warning: Having the script name be the same as a built-in type is usually not desired."), EditorValidationPanel::MSG_WARNING, false); + } path_controls[0]->set_visible(!is_built_in); path_controls[1]->set_visible(!is_built_in); @@ -761,7 +741,6 @@ void ScriptCreateDialog::_update_dialog() { // Check if the script name is the same as the parent class. // This warning isn't relevant if the script is built-in. - script_name_warning_label->set_visible(!is_built_in && _get_class_name() == parent_name->get_text()); bool is_new_file = is_built_in || is_new_script_created; @@ -774,21 +753,16 @@ void ScriptCreateDialog::_update_dialog() { if (is_new_file) { if (is_built_in) { - _msg_path_valid(true, TTR("Built-in script (into scene file).")); - } - if (is_new_script_created && is_path_valid) { - _msg_path_valid(true, TTR("Will create a new script file.")); + validation_panel->set_message(MSG_ID_PATH, TTR("Built-in script (into scene file)."), EditorValidationPanel::MSG_OK); } } else { + template_inactive_message = TTR("Using existing script file."); if (load_enabled) { - template_inactive_message = TTR("Using existing script file."); if (is_path_valid) { - _msg_path_valid(true, TTR("Will load an existing script file.")); + validation_panel->set_message(MSG_ID_PATH, TTR("Will load an existing script file."), EditorValidationPanel::MSG_OK); } } else { - template_inactive_message = TTR("Using existing script file."); - _msg_path_valid(false, TTR("Script file already exists.")); - script_ok = false; + validation_panel->set_message(MSG_ID_PATH, TTR("Script file already exists."), EditorValidationPanel::MSG_ERROR); } } @@ -806,18 +780,7 @@ void ScriptCreateDialog::_update_dialog() { template_menu->set_disabled(true); template_menu->clear(); template_menu->add_item(template_inactive_message); - } - template_info_label->set_visible(!template_menu->is_disabled()); - - get_ok_button()->set_disabled(!script_ok); - - Callable entered_call = callable_mp(this, &ScriptCreateDialog::_path_submitted); - if (script_ok) { - if (!file_path->is_connected("text_submitted", entered_call)) { - file_path->connect("text_submitted", entered_call); - } - } else if (file_path->is_connected("text_submitted", entered_call)) { - file_path->disconnect("text_submitted", entered_call); + validation_panel->set_message(MSG_ID_TEMPLATE, "", EditorValidationPanel::MSG_INFO); } } @@ -967,47 +930,23 @@ ScriptCreateDialog::ScriptCreateDialog() { /* Information Messages Field */ - VBoxContainer *vb = memnew(VBoxContainer); - - error_label = memnew(Label); - vb->add_child(error_label); - - path_error_label = memnew(Label); - vb->add_child(path_error_label); - - builtin_warning_label = memnew(Label); - builtin_warning_label->set_text( - TTR("Note: Built-in scripts have some limitations and can't be edited using an external editor.")); - vb->add_child(builtin_warning_label); - builtin_warning_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); - builtin_warning_label->hide(); - - script_name_warning_label = memnew(Label); - script_name_warning_label->set_text( - TTR("Warning: Having the script name be the same as a built-in type is usually not desired.")); - vb->add_child(script_name_warning_label); - script_name_warning_label->add_theme_color_override("font_color", Color(1, 0.85, 0.4)); - script_name_warning_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); - script_name_warning_label->hide(); - - template_info_label = memnew(Label); - vb->add_child(template_info_label); - template_info_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); - - status_panel = memnew(PanelContainer); - status_panel->set_h_size_flags(Control::SIZE_FILL); - status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); - status_panel->add_child(vb); + validation_panel = memnew(EditorValidationPanel); + validation_panel->add_line(MSG_ID_SCRIPT, TTR("Script path/name is valid.")); + validation_panel->add_line(MSG_ID_PATH, TTR("Will create a new script file.")); + validation_panel->add_line(MSG_ID_BUILT_IN); + validation_panel->add_line(MSG_ID_TEMPLATE); + validation_panel->set_update_callback(callable_mp(this, &ScriptCreateDialog::_update_dialog)); + validation_panel->set_accept_button(get_ok_button()); /* Spacing */ Control *spacing = memnew(Control); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); - vb = memnew(VBoxContainer); + VBoxContainer *vb = memnew(VBoxContainer); vb->add_child(gc); vb->add_child(spacing); - vb->add_child(status_panel); + vb->add_child(validation_panel); add_child(vb); /* Language */ diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h index fa42b967462..ea2f480c579 100644 --- a/editor/script_create_dialog.h +++ b/editor/script_create_dialog.h @@ -41,17 +41,20 @@ class CreateDialog; class EditorFileDialog; +class EditorValidationPanel; class ScriptCreateDialog : public ConfirmationDialog { GDCLASS(ScriptCreateDialog, ConfirmationDialog); + enum { + MSG_ID_SCRIPT, + MSG_ID_PATH, + MSG_ID_BUILT_IN, + MSG_ID_TEMPLATE, + }; + LineEdit *class_name = nullptr; - Label *error_label = nullptr; - Label *path_error_label = nullptr; - Label *builtin_warning_label = nullptr; - Label *script_name_warning_label = nullptr; - Label *template_info_label = nullptr; - PanelContainer *status_panel = nullptr; + EditorValidationPanel *validation_panel = nullptr; LineEdit *parent_name = nullptr; Button *parent_browse_button = nullptr; Button *parent_search_button = nullptr; @@ -67,6 +70,7 @@ class ScriptCreateDialog : public ConfirmationDialog { AcceptDialog *alert = nullptr; CreateDialog *select_class = nullptr; bool is_browsing_parent = false; + String path_error; String template_inactive_message; String initial_bp; bool is_new_script_created = true; @@ -113,8 +117,6 @@ class ScriptCreateDialog : public ConfirmationDialog { virtual void ok_pressed() override; void _create_new(); void _load_exist(); - void _msg_script_valid(bool valid, const String &p_msg = String()); - void _msg_path_valid(bool valid, const String &p_msg = String()); void _update_template_menu(); void _update_dialog(); ScriptLanguage::ScriptTemplate _get_current_template() const; diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 607b53718bf..9a7b9bc84d6 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "editor/editor_scale.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/gui/editor_validation_panel.h" #include "scene/resources/shader_include.h" #include "scene/resources/visual_shader.h" #include "servers/rendering/shader_types.h" @@ -89,7 +90,6 @@ void ShaderCreateDialog::_update_theme() { } path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); - status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } void ShaderCreateDialog::_update_language_info() { @@ -147,7 +147,7 @@ void ShaderCreateDialog::ok_pressed() { } is_new_shader_created = true; - _update_dialog(); + validation_panel->update(); } void ShaderCreateDialog::_create_new() { @@ -327,7 +327,7 @@ void ShaderCreateDialog::_type_changed(int p_language) { } EditorSettings::get_singleton()->set_project_metadata("shader_setup", "last_selected_language", type_menu->get_item_text(type_menu->get_selected())); - _update_dialog(); + validation_panel->update(); } void ShaderCreateDialog::_built_in_toggled(bool p_enabled) { @@ -337,7 +337,7 @@ void ShaderCreateDialog::_built_in_toggled(bool p_enabled) { } else { _path_changed(file_path->get_text()); } - _update_dialog(); + validation_panel->update(); } void ShaderCreateDialog::_browse_path() { @@ -378,10 +378,9 @@ void ShaderCreateDialog::_path_changed(const String &p_path) { is_path_valid = false; is_new_shader_created = true; - String path_error = _validate_path(p_path); + path_error = _validate_path(p_path); if (!path_error.is_empty()) { - _msg_path_valid(false, path_error); - _update_dialog(); + validation_panel->update(); return; } @@ -389,15 +388,16 @@ void ShaderCreateDialog::_path_changed(const String &p_path) { String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges()); if (f->file_exists(p)) { is_new_shader_created = false; - _msg_path_valid(true, TTR("File exists, it will be reused.")); } is_path_valid = true; - _update_dialog(); + validation_panel->update(); } void ShaderCreateDialog::_path_submitted(const String &p_path) { - ok_pressed(); + if (!get_ok_button()->is_disabled()) { + ok_pressed(); + } } void ShaderCreateDialog::config(const String &p_base_path, bool p_built_in_enabled, bool p_load_enabled, int p_preferred_type, int p_preferred_mode) { @@ -490,33 +490,14 @@ String ShaderCreateDialog::_validate_path(const String &p_path) { return ""; } -void ShaderCreateDialog::_msg_script_valid(bool valid, const String &p_msg) { - error_label->set_text(String::utf8("• ") + p_msg); - if (valid) { - error_label->add_theme_color_override("font_color", gc->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - } else { - error_label->add_theme_color_override("font_color", gc->get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } -} - -void ShaderCreateDialog::_msg_path_valid(bool valid, const String &p_msg) { - path_error_label->set_text(String::utf8("• ") + p_msg); - if (valid) { - path_error_label->add_theme_color_override("font_color", gc->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - } else { - path_error_label->add_theme_color_override("font_color", gc->get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } -} - void ShaderCreateDialog::_update_dialog() { - bool shader_ok = true; - if (!is_built_in && !is_path_valid) { - _msg_script_valid(false, TTR("Invalid path.")); - shader_ok = false; + validation_panel->set_message(MSG_ID_SHADER, TTR("Invalid path."), EditorValidationPanel::MSG_ERROR); } - if (shader_ok) { - _msg_script_valid(true, TTR("Shader path/name is valid.")); + if (!path_error.is_empty()) { + validation_panel->set_message(MSG_ID_PATH, path_error, EditorValidationPanel::MSG_ERROR); + } else if (validation_panel->is_valid() && !is_new_shader_created) { + validation_panel->set_message(MSG_ID_SHADER, TTR("File exists, it will be reused."), EditorValidationPanel::MSG_OK); } if (!built_in_enabled) { internal->set_pressed(false); @@ -537,37 +518,23 @@ void ShaderCreateDialog::_update_dialog() { internal->set_disabled(!built_in_enabled); - builtin_warning_label->set_visible(is_built_in); + if (is_built_in) { + validation_panel->set_message(MSG_ID_BUILT_IN, TTR("Note: Built-in shaders can't be edited using an external editor."), EditorValidationPanel::MSG_INFO, false); + } if (is_built_in) { set_ok_button_text(TTR("Create")); - _msg_path_valid(true, TTR("Built-in shader (into scene file).")); + validation_panel->set_message(MSG_ID_PATH, TTR("Built-in shader (into scene file)."), EditorValidationPanel::MSG_OK); } else if (is_new_shader_created) { set_ok_button_text(TTR("Create")); - if (is_path_valid) { - _msg_path_valid(true, TTR("Will create a new shader file.")); - } } else if (load_enabled) { set_ok_button_text(TTR("Load")); if (is_path_valid) { - _msg_path_valid(true, TTR("Will load an existing shader file.")); + validation_panel->set_message(MSG_ID_PATH, TTR("Will load an existing shader file."), EditorValidationPanel::MSG_OK); } } else { set_ok_button_text(TTR("Create")); - _msg_path_valid(false, TTR("Shader file already exists.")); - - shader_ok = false; - } - - get_ok_button()->set_disabled(!shader_ok); - - Callable entered_call = callable_mp(this, &ShaderCreateDialog::_path_submitted); - if (shader_ok) { - if (!file_path->is_connected("text_submitted", entered_call)) { - file_path->connect("text_submitted", entered_call); - } - } else if (file_path->is_connected("text_submitted", entered_call)) { - file_path->disconnect("text_submitted", entered_call); + validation_panel->set_message(MSG_ID_PATH, TTR("Shader file already exists."), EditorValidationPanel::MSG_ERROR); } } @@ -588,35 +555,22 @@ ShaderCreateDialog::ShaderCreateDialog() { // Error Fields. - VBoxContainer *vb = memnew(VBoxContainer); - - error_label = memnew(Label); - vb->add_child(error_label); - - path_error_label = memnew(Label); - vb->add_child(path_error_label); - - builtin_warning_label = memnew(Label); - builtin_warning_label->set_text( - TTR("Note: Built-in shaders can't be edited using an external editor.")); - vb->add_child(builtin_warning_label); - builtin_warning_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); - builtin_warning_label->hide(); - - status_panel = memnew(PanelContainer); - status_panel->set_h_size_flags(Control::SIZE_FILL); - status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); - status_panel->add_child(vb); + validation_panel = memnew(EditorValidationPanel); + validation_panel->add_line(MSG_ID_SHADER, TTR("Shader path/name is valid.")); + validation_panel->add_line(MSG_ID_PATH, TTR("Will create a new shader file.")); + validation_panel->add_line(MSG_ID_BUILT_IN); + validation_panel->set_update_callback(callable_mp(this, &ShaderCreateDialog::_update_dialog)); + validation_panel->set_accept_button(get_ok_button()); // Spacing. Control *spacing = memnew(Control); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); - vb = memnew(VBoxContainer); + VBoxContainer *vb = memnew(VBoxContainer); vb->add_child(gc); vb->add_child(spacing); - vb->add_child(status_panel); + vb->add_child(validation_panel); add_child(vb); // Type. diff --git a/editor/shader_create_dialog.h b/editor/shader_create_dialog.h index 729a7b5bd4c..d6d9f100201 100644 --- a/editor/shader_create_dialog.h +++ b/editor/shader_create_dialog.h @@ -40,10 +40,17 @@ #include "scene/gui/panel_container.h" class EditorFileDialog; +class EditorValidationPanel; class ShaderCreateDialog : public ConfirmationDialog { GDCLASS(ShaderCreateDialog, ConfirmationDialog); + enum { + MSG_ID_SHADER, + MSG_ID_PATH, + MSG_ID_BUILT_IN, + }; + struct ShaderTypeData { List extensions; String default_extension; @@ -53,10 +60,7 @@ class ShaderCreateDialog : public ConfirmationDialog { List type_data; GridContainer *gc = nullptr; - Label *error_label = nullptr; - Label *path_error_label = nullptr; - Label *builtin_warning_label = nullptr; - PanelContainer *status_panel = nullptr; + EditorValidationPanel *validation_panel = nullptr; OptionButton *type_menu = nullptr; OptionButton *mode_menu = nullptr; OptionButton *template_menu = nullptr; @@ -67,6 +71,7 @@ class ShaderCreateDialog : public ConfirmationDialog { AcceptDialog *alert = nullptr; String initial_base_path; + String path_error; bool is_new_shader_created = true; bool is_path_valid = false; bool is_built_in = false; @@ -93,8 +98,6 @@ class ShaderCreateDialog : public ConfirmationDialog { virtual void ok_pressed() override; void _create_new(); void _load_exist(); - void _msg_script_valid(bool valid, const String &p_msg = String()); - void _msg_path_valid(bool valid, const String &p_msg = String()); void _update_dialog(); protected: