Standardize dialog input validation as a new class

This commit is contained in:
kobewi 2023-06-26 19:18:27 +02:00
parent eca6f0eb54
commit 7f41403a6f
13 changed files with 382 additions and 341 deletions

View File

@ -33,10 +33,10 @@
#include "core/io/dir_access.h" #include "core/io/dir_access.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/gui/editor_validation_panel.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
#include "scene/gui/label.h" #include "scene/gui/label.h"
#include "scene/gui/line_edit.h" #include "scene/gui/line_edit.h"
#include "scene/gui/panel_container.h"
static String sanitize_input(const String &p_path) { static String sanitize_input(const String &p_path) {
String path = p_path.strip_edges(); String path = p_path.strip_edges();
@ -73,24 +73,17 @@ String DirectoryCreateDialog::_validate_path(const String &p_path) const {
return String(); return String();
} }
void DirectoryCreateDialog::_on_dir_path_changed(const String &p_text) { void DirectoryCreateDialog::_on_dir_path_changed() {
const String path = sanitize_input(p_text); const String path = sanitize_input(dir_path->get_text());
const String error = _validate_path(path); const String error = _validate_path(path);
if (error.is_empty()) { if (error.is_empty()) {
status_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor")));
if (path.contains("/")) { if (path.contains("/")) {
status_label->set_text(TTR("Using slashes in folder names will create subfolders recursively.")); validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK);
} else {
status_label->set_text(TTR("Folder name is valid."));
} }
} else { } else {
status_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR);
status_label->set_text(error);
} }
get_ok_button()->set_disabled(!error.is_empty());
} }
void DirectoryCreateDialog::ok_pressed() { 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)); label->set_text(vformat(TTR("Create new folder in %s:"), base_dir));
dir_path->set_text("new folder"); dir_path->set_text("new folder");
dir_path->select_all(); dir_path->select_all();
_on_dir_path_changed(dir_path->get_text()); validation_panel->update();
} }
void DirectoryCreateDialog::_bind_methods() { void DirectoryCreateDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("dir_created")); 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() { DirectoryCreateDialog::DirectoryCreateDialog() {
set_title(TTR("Create Folder")); set_title(TTR("Create Folder"));
set_min_size(Size2i(480, 0) * EDSCALE); set_min_size(Size2i(480, 0) * EDSCALE);
@ -154,7 +139,6 @@ DirectoryCreateDialog::DirectoryCreateDialog() {
vb->add_child(label); vb->add_child(label);
dir_path = memnew(LineEdit); dir_path = memnew(LineEdit);
dir_path->connect("text_changed", callable_mp(this, &DirectoryCreateDialog::_on_dir_path_changed));
vb->add_child(dir_path); vb->add_child(dir_path);
register_text_enter(dir_path); register_text_enter(dir_path);
@ -162,11 +146,11 @@ DirectoryCreateDialog::DirectoryCreateDialog() {
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
vb->add_child(spacing); vb->add_child(spacing);
status_panel = memnew(PanelContainer); validation_panel = memnew(EditorValidationPanel);
status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); vb->add_child(validation_panel);
vb->add_child(status_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); dir_path->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
status_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
status_panel->add_child(status_label);
} }

View File

@ -33,9 +33,9 @@
#include "scene/gui/dialogs.h" #include "scene/gui/dialogs.h"
class EditorValidationPanel;
class Label; class Label;
class LineEdit; class LineEdit;
class PanelContainer;
class DirectoryCreateDialog : public ConfirmationDialog { class DirectoryCreateDialog : public ConfirmationDialog {
GDCLASS(DirectoryCreateDialog, ConfirmationDialog); GDCLASS(DirectoryCreateDialog, ConfirmationDialog);
@ -44,17 +44,13 @@ class DirectoryCreateDialog : public ConfirmationDialog {
Label *label = nullptr; Label *label = nullptr;
LineEdit *dir_path = nullptr; LineEdit *dir_path = nullptr;
EditorValidationPanel *validation_panel = nullptr;
PanelContainer *status_panel = nullptr;
Label *status_label = nullptr;
String _validate_path(const String &p_path) const; String _validate_path(const String &p_path) const;
void _on_dir_path_changed();
void _on_dir_path_changed(const String &p_text);
protected: protected:
static void _bind_methods(); static void _bind_methods();
void _notification(int p_what);
virtual void ok_pressed() override; virtual void ok_pressed() override;
virtual void _post_popup() override; virtual void _post_popup() override;

View File

@ -38,6 +38,7 @@
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h" #include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_validation_panel.h"
#include "editor/inspector_dock.h" #include "editor/inspector_dock.h"
#include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h"
#include "multi_node_edit.h" #include "multi_node_edit.h"
@ -3927,12 +3928,6 @@ void EditorInspector::_notification(int p_what) {
} }
} break; } 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: { case NOTIFICATION_PREDELETE: {
edit(nullptr); //just in case edit(nullptr); //just in case
} break; } break;
@ -4083,27 +4078,17 @@ void EditorInspector::_add_meta_confirm() {
undo_redo->commit_action(); undo_redo->commit_action();
} }
void EditorInspector::_check_meta_name(const String &p_name) { void EditorInspector::_check_meta_name() {
String error; const String meta_name = add_meta_name->get_text();
if (p_name == "") { if (meta_name.is_empty()) {
error = TTR("Metadata name can't be empty."); validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR);
} else if (!p_name.is_valid_identifier()) { } else if (!meta_name.is_valid_identifier()) {
error = TTR("Metadata name must be a 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(p_name)) { } else if (object->has_meta(meta_name)) {
error = vformat(TTR("Metadata with name \"%s\" already exists."), p_name); validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR);
} else if (p_name[0] == '_') { } else if (meta_name[0] == '_') {
error = TTR("Names starting with _ are reserved for editor-only metadata."); validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata."), EditorValidationPanel::MSG_ERROR);
}
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);
} }
} }
@ -4143,16 +4128,13 @@ void EditorInspector::_show_add_meta_dialog() {
add_meta_dialog->register_text_enter(add_meta_name); add_meta_dialog->register_text_enter(add_meta_name);
add_meta_dialog->connect("confirmed", callable_mp(this, &EditorInspector::_add_meta_confirm)); add_meta_dialog->connect("confirmed", callable_mp(this, &EditorInspector::_add_meta_confirm));
add_meta_error_panel = memnew(PanelContainer); validation_panel = memnew(EditorValidationPanel);
vbc->add_child(add_meta_error_panel); vbc->add_child(validation_panel);
if (is_inside_tree()) { validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid."));
add_meta_error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); 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_name->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
add_meta_error_panel->add_child(add_meta_error);
add_meta_name->connect("text_changed", callable_mp(this, &EditorInspector::_check_meta_name));
} }
Node *node = Object::cast_to<Node>(object); Node *node = Object::cast_to<Node>(object);
@ -4164,9 +4146,9 @@ void EditorInspector::_show_add_meta_dialog() {
} }
add_meta_dialog->popup_centered(); add_meta_dialog->popup_centered();
add_meta_name->set_text("");
_check_meta_name("");
add_meta_name->grab_focus(); add_meta_name->grab_focus();
add_meta_name->set_text("");
validation_panel->update();
} }
void EditorInspector::_bind_methods() { void EditorInspector::_bind_methods() {

View File

@ -39,6 +39,7 @@ class AcceptDialog;
class Button; class Button;
class ConfirmationDialog; class ConfirmationDialog;
class EditorInspector; class EditorInspector;
class EditorValidationPanel;
class LineEdit; class LineEdit;
class OptionButton; class OptionButton;
class PanelContainer; class PanelContainer;
@ -543,12 +544,11 @@ class EditorInspector : public ScrollContainer {
ConfirmationDialog *add_meta_dialog = nullptr; ConfirmationDialog *add_meta_dialog = nullptr;
LineEdit *add_meta_name = nullptr; LineEdit *add_meta_name = nullptr;
OptionButton *add_meta_type = nullptr; OptionButton *add_meta_type = nullptr;
PanelContainer *add_meta_error_panel = nullptr; EditorValidationPanel *validation_panel = nullptr;
Label *add_meta_error = nullptr;
void _add_meta_confirm(); void _add_meta_confirm();
void _show_add_meta_dialog(); void _show_add_meta_dialog();
void _check_meta_name(const String &p_name); void _check_meta_name();
protected: protected:
static void _bind_methods(); static void _bind_methods();

View File

@ -1280,6 +1280,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
} }
theme->set_stylebox("panel", "Tree", style_tree_bg); theme->set_stylebox("panel", "Tree", style_tree_bg);
theme->set_stylebox("panel", "EditorValidationPanel", style_tree_bg);
// Tree // Tree
theme->set_icon("checked", "Tree", theme->get_icon(SNAME("GuiChecked"), SNAME("EditorIcons"))); theme->set_icon("checked", "Tree", theme->get_icon(SNAME("GuiChecked"), SNAME("EditorIcons")));

View File

@ -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<int, String> &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);
}

View File

@ -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<int, String> valid_messages;
HashMap<int, Label *> 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

View File

@ -34,6 +34,7 @@
#include "editor/create_dialog.h" #include "editor/create_dialog.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/gui/editor_validation_panel.h"
#include "scene/2d/node_2d.h" #include "scene/2d/node_2d.h"
#include "scene/3d/node_3d.h" #include "scene/3d/node_3d.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
@ -41,7 +42,6 @@
#include "scene/gui/grid_container.h" #include "scene/gui/grid_container.h"
#include "scene/gui/line_edit.h" #include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h" #include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
#include "scene/resources/packed_scene.h" #include "scene/resources/packed_scene.h"
void SceneCreateDialog::_notification(int p_what) { 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_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons")));
node_type_gui->set_icon(get_theme_icon(SNAME("Control"), 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"))); 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; } break;
} }
} }
@ -63,7 +62,7 @@ void SceneCreateDialog::config(const String &p_dir) {
root_name_edit->set_text(""); root_name_edit->set_text("");
scene_name_edit->set_text(""); scene_name_edit->set_text("");
scene_name_edit->call_deferred(SNAME("grab_focus")); scene_name_edit->call_deferred(SNAME("grab_focus"));
update_dialog(); validation_panel->update();
} }
void SceneCreateDialog::accept_create() { void SceneCreateDialog::accept_create() {
@ -82,40 +81,35 @@ void SceneCreateDialog::browse_types() {
void SceneCreateDialog::on_type_picked() { void SceneCreateDialog::on_type_picked() {
other_type_display->set_text(select_node_dialog->get_selected_type().get_slice(" ", 0)); other_type_display->set_text(select_node_dialog->get_selected_type().get_slice(" ", 0));
if (node_type_other->is_pressed()) { if (node_type_other->is_pressed()) {
update_dialog(); validation_panel->update();
} else { } 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() { void SceneCreateDialog::update_dialog() {
scene_name = scene_name_edit->get_text().strip_edges(); 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()) { if (scene_name.is_empty()) {
update_error(file_error_label, MSG_ERROR, TTR("Scene name is empty.")); validation_panel->set_message(MSG_ID_PATH, TTR("Scene name is empty."), EditorValidationPanel::MSG_ERROR);
is_valid = false;
} }
if (is_valid) { if (validation_panel->is_valid()) {
if (!scene_name.ends_with(".")) { if (!scene_name.ends_with(".")) {
scene_name += "."; scene_name += ".";
} }
scene_name += scene_extension_picker->get_selected_metadata().operator String(); scene_name += scene_extension_picker->get_selected_metadata().operator String();
} }
if (is_valid && !scene_name.is_valid_filename()) { if (validation_panel->is_valid() && !scene_name.is_valid_filename()) {
update_error(file_error_label, MSG_ERROR, TTR("File name invalid.")); validation_panel->set_message(MSG_ID_PATH, TTR("File name invalid."), EditorValidationPanel::MSG_ERROR);
is_valid = false;
} }
if (is_valid) { if (validation_panel->is_valid()) {
scene_name = directory.path_join(scene_name); scene_name = directory.path_join(scene_name);
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (da->file_exists(scene_name)) { if (da->file_exists(scene_name)) {
update_error(file_error_label, MSG_ERROR, TTR("File already exists.")); validation_panel->set_message(MSG_ID_PATH, TTR("File already exists."), EditorValidationPanel::MSG_ERROR);
is_valid = false;
} }
} }
@ -126,8 +120,6 @@ void SceneCreateDialog::update_dialog() {
node_type_other->set_icon(nullptr); 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(); root_name = root_name_edit->get_text().strip_edges();
if (root_name.is_empty()) { if (root_name.is_empty()) {
root_name = scene_name_edit->get_text().strip_edges(); root_name = scene_name_edit->get_text().strip_edges();
@ -135,39 +127,16 @@ void SceneCreateDialog::update_dialog() {
if (root_name.is_empty()) { if (root_name.is_empty()) {
root_name_edit->set_placeholder(TTR("Leave empty to derive from scene name")); root_name_edit->set_placeholder(TTR("Leave empty to derive from scene name"));
} else { } else {
// Respect the desired root node casing from ProjectSettings and ensure it's a valid node name. // Respect the desired root node casing from ProjectSettings.
String adjusted_root_name = Node::adjust_name_casing(root_name); root_name = Node::adjust_name_casing(root_name);
root_name = adjusted_root_name.validate_node_name(); root_name_edit->set_placeholder(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);
} }
} }
if (root_name.is_empty() || root_name.validate_node_name() != root_name) { if (root_name.is_empty()) {
update_error(node_error_label, MSG_ERROR, TTR("Invalid root node name.")); validation_panel->set_message(MSG_ID_ROOT, TTR("Invalid root node name."), EditorValidationPanel::MSG_ERROR);
is_valid = false; } 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);
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;
} }
} }
@ -268,8 +237,6 @@ SceneCreateDialog::SceneCreateDialog() {
select_node_button = memnew(Button); select_node_button = memnew(Button);
hb->add_child(select_node_button); hb->add_child(select_node_button);
select_node_button->connect("pressed", callable_mp(this, &SceneCreateDialog::browse_types)); 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); scene_name_edit = memnew(LineEdit);
hb->add_child(scene_name_edit); hb->add_child(scene_name_edit);
scene_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); 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)); scene_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1));
List<String> extensions; List<String> extensions;
@ -305,7 +271,6 @@ SceneCreateDialog::SceneCreateDialog() {
gc->add_child(root_name_edit); 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_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->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)); 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); main_vb->add_child(spacing);
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
status_panel = memnew(PanelContainer); validation_panel = memnew(EditorValidationPanel);
main_vb->add_child(status_panel); main_vb->add_child(validation_panel);
status_panel->set_h_size_flags(Control::SIZE_FILL); validation_panel->add_line(MSG_ID_PATH, TTR("Scene name is valid."));
status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); 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); node_type_group->connect("pressed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
status_panel->add_child(status_vb); 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));
file_error_label = memnew(Label);
status_vb->add_child(file_error_label);
node_error_label = memnew(Label);
status_vb->add_child(node_error_label);
set_title(TTR("Create New Scene")); set_title(TTR("Create New Scene"));
set_min_size(Size2i(400 * EDSCALE, 0)); set_min_size(Size2i(400 * EDSCALE, 0));

View File

@ -37,18 +37,17 @@ class ButtonGroup;
class CheckBox; class CheckBox;
class CreateDialog; class CreateDialog;
class EditorFileDialog; class EditorFileDialog;
class EditorValidationPanel;
class Label; class Label;
class LineEdit; class LineEdit;
class OptionButton; class OptionButton;
class PanelContainer;
class SceneCreateDialog : public ConfirmationDialog { class SceneCreateDialog : public ConfirmationDialog {
GDCLASS(SceneCreateDialog, ConfirmationDialog); GDCLASS(SceneCreateDialog, ConfirmationDialog);
enum MsgType { enum {
MSG_OK, MSG_ID_PATH,
MSG_ERROR, MSG_ID_ROOT,
MSG_WARNING,
}; };
const StringName type_meta = StringName("type"); const StringName type_meta = StringName("type");
@ -80,15 +79,12 @@ private:
OptionButton *scene_extension_picker = nullptr; OptionButton *scene_extension_picker = nullptr;
LineEdit *root_name_edit = nullptr; LineEdit *root_name_edit = nullptr;
PanelContainer *status_panel = nullptr; EditorValidationPanel *validation_panel = nullptr;
Label *file_error_label = nullptr;
Label *node_error_label = nullptr;
void accept_create(); void accept_create();
void browse_types(); void browse_types();
void on_type_picked(); void on_type_picked();
void update_dialog(); void update_dialog();
void update_error(Label *p_label, MsgType p_type, const String &p_msg);
protected: protected:
void _notification(int p_what); void _notification(int p_what);

View File

@ -41,6 +41,7 @@
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/gui/editor_file_dialog.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) { static String _get_parent_class_of_script(String p_path) {
if (!ResourceLoader::exists(p_path, "Script")) { 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"))); path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")));
parent_browse_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"))); 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; } break;
} }
} }
@ -295,13 +295,7 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must
} }
// Let ScriptLanguage do custom validation. // Let ScriptLanguage do custom validation.
String path_error = ScriptServer::get_language(language_menu->get_selected())->validate_path(p); return ScriptServer::get_language(language_menu->get_selected())->validate_path(p);
if (!path_error.is_empty()) {
return path_error;
}
// All checks passed.
return "";
} }
String ScriptCreateDialog::_get_class_name() const { 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) { void ScriptCreateDialog::_class_name_changed(const String &p_name) {
is_class_name_valid = _validate_class(class_name->get_text()); is_class_name_valid = _validate_class(class_name->get_text());
_update_dialog(); validation_panel->update();
} }
void ScriptCreateDialog::_parent_name_changed(const String &p_parent) { void ScriptCreateDialog::_parent_name_changed(const String &p_parent) {
is_parent_name_valid = _validate_parent(parent_name->get_text()); is_parent_name_valid = _validate_parent(parent_name->get_text());
_update_dialog(); validation_panel->update();
} }
void ScriptCreateDialog::_template_changed(int p_template) { void ScriptCreateDialog::_template_changed(int p_template) {
@ -347,6 +341,7 @@ void ScriptCreateDialog::_template_changed(int p_template) {
} }
} }
} }
// Update template label information. // Update template label information.
String template_info = U""; String template_info = U"";
template_info += TTR("Template:"); template_info += TTR("Template:");
@ -354,8 +349,7 @@ void ScriptCreateDialog::_template_changed(int p_template) {
if (!sinfo.description.is_empty()) { if (!sinfo.description.is_empty()) {
template_info += " - " + sinfo.description; template_info += " - " + sinfo.description;
} }
template_info_label->set_text(template_info); validation_panel->set_message(MSG_ID_TEMPLATE, template_info, EditorValidationPanel::MSG_INFO, false);
template_info_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor")));
} }
void ScriptCreateDialog::ok_pressed() { void ScriptCreateDialog::ok_pressed() {
@ -367,7 +361,7 @@ void ScriptCreateDialog::ok_pressed() {
EditorSettings::get_singleton()->save(); EditorSettings::get_singleton()->save();
is_new_script_created = true; is_new_script_created = true;
_update_dialog(); validation_panel->update();
} }
void ScriptCreateDialog::_create_new() { 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())); 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()); _parent_name_changed(parent_name->get_text());
_update_dialog(); validation_panel->update();
} }
void ScriptCreateDialog::_built_in_pressed() { void ScriptCreateDialog::_built_in_pressed() {
@ -482,13 +476,13 @@ void ScriptCreateDialog::_built_in_pressed() {
is_built_in = false; is_built_in = false;
_path_changed(file_path->get_text()); _path_changed(file_path->get_text());
} }
_update_dialog(); validation_panel->update();
} }
void ScriptCreateDialog::_use_template_pressed() { void ScriptCreateDialog::_use_template_pressed() {
is_using_templates = use_templates->is_pressed(); is_using_templates = use_templates->is_pressed();
EditorSettings::get_singleton()->set_meta("script_setup_use_script_templates", is_using_templates); 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) { 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_path_valid = false;
is_new_script_created = true; 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()) { if (!path_error.is_empty()) {
_msg_path_valid(false, path_error); validation_panel->update();
_update_dialog();
return; return;
} }
@ -567,32 +560,15 @@ void ScriptCreateDialog::_path_changed(const String &p_path) {
String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges()); String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges());
if (da->file_exists(p)) { if (da->file_exists(p)) {
is_new_script_created = false; is_new_script_created = false;
_msg_path_valid(true, TTR("File exists, it will be reused."));
} }
is_path_valid = true; is_path_valid = true;
_update_dialog(); validation_panel->update();
} }
void ScriptCreateDialog::_path_submitted(const String &p_path) { void ScriptCreateDialog::_path_submitted(const String &p_path) {
ok_pressed(); if (!get_ok_button()->is_disabled()) {
} 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")));
} }
} }
@ -688,25 +664,25 @@ void ScriptCreateDialog::_update_template_menu() {
void ScriptCreateDialog::_update_dialog() { void ScriptCreateDialog::_update_dialog() {
// "Add Script Dialog" GUI logic and script checks. // "Add Script Dialog" GUI logic and script checks.
_update_template_menu(); _update_template_menu();
bool script_ok = true;
// Is script path/name valid (order from top to bottom)? // Is script path/name valid (order from top to bottom)?
if (!is_built_in && !is_path_valid) { if (!is_built_in && !is_path_valid) {
_msg_script_valid(false, TTR("Invalid path.")); validation_panel->set_message(MSG_ID_SCRIPT, TTR("Invalid path."), EditorValidationPanel::MSG_ERROR);
script_ok = false;
} }
if (has_named_classes && (is_new_script_created && !is_class_name_valid)) { if (has_named_classes && (is_new_script_created && !is_class_name_valid)) {
_msg_script_valid(false, TTR("Invalid class name.")); validation_panel->set_message(MSG_ID_SCRIPT, TTR("Invalid class name."), EditorValidationPanel::MSG_ERROR);
script_ok = false;
} }
if (!is_parent_name_valid && is_new_script_created) { if (!is_parent_name_valid && is_new_script_created) {
_msg_script_valid(false, TTR("Invalid inherited parent name or path.")); validation_panel->set_message(MSG_ID_SCRIPT, TTR("Invalid inherited parent name or path."), EditorValidationPanel::MSG_ERROR);
script_ok = false;
} }
if (script_ok) { if (validation_panel->is_valid() && !is_new_script_created) {
_msg_script_valid(true, TTR("Script path/name is valid.")); 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? // Does script have named classes?
@ -752,7 +728,11 @@ void ScriptCreateDialog::_update_dialog() {
// Is Script created or loaded from existing file? // 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[0]->set_visible(!is_built_in);
path_controls[1]->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. // Check if the script name is the same as the parent class.
// This warning isn't relevant if the script is built-in. // 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; 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_new_file) {
if (is_built_in) { if (is_built_in) {
_msg_path_valid(true, TTR("Built-in script (into scene file).")); validation_panel->set_message(MSG_ID_PATH, TTR("Built-in script (into scene file)."), EditorValidationPanel::MSG_OK);
}
if (is_new_script_created && is_path_valid) {
_msg_path_valid(true, TTR("Will create a new script file."));
} }
} else { } else {
template_inactive_message = TTR("Using existing script file.");
if (load_enabled) { if (load_enabled) {
template_inactive_message = TTR("Using existing script file.");
if (is_path_valid) { 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 { } else {
template_inactive_message = TTR("Using existing script file."); validation_panel->set_message(MSG_ID_PATH, TTR("Script file already exists."), EditorValidationPanel::MSG_ERROR);
_msg_path_valid(false, TTR("Script file already exists."));
script_ok = false;
} }
} }
@ -806,18 +780,7 @@ void ScriptCreateDialog::_update_dialog() {
template_menu->set_disabled(true); template_menu->set_disabled(true);
template_menu->clear(); template_menu->clear();
template_menu->add_item(template_inactive_message); template_menu->add_item(template_inactive_message);
} validation_panel->set_message(MSG_ID_TEMPLATE, "", EditorValidationPanel::MSG_INFO);
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);
} }
} }
@ -967,47 +930,23 @@ ScriptCreateDialog::ScriptCreateDialog() {
/* Information Messages Field */ /* Information Messages Field */
VBoxContainer *vb = memnew(VBoxContainer); validation_panel = memnew(EditorValidationPanel);
validation_panel->add_line(MSG_ID_SCRIPT, TTR("Script path/name is valid."));
error_label = memnew(Label); validation_panel->add_line(MSG_ID_PATH, TTR("Will create a new script file."));
vb->add_child(error_label); validation_panel->add_line(MSG_ID_BUILT_IN);
validation_panel->add_line(MSG_ID_TEMPLATE);
path_error_label = memnew(Label); validation_panel->set_update_callback(callable_mp(this, &ScriptCreateDialog::_update_dialog));
vb->add_child(path_error_label); validation_panel->set_accept_button(get_ok_button());
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);
/* Spacing */ /* Spacing */
Control *spacing = memnew(Control); Control *spacing = memnew(Control);
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
vb = memnew(VBoxContainer); VBoxContainer *vb = memnew(VBoxContainer);
vb->add_child(gc); vb->add_child(gc);
vb->add_child(spacing); vb->add_child(spacing);
vb->add_child(status_panel); vb->add_child(validation_panel);
add_child(vb); add_child(vb);
/* Language */ /* Language */

View File

@ -41,17 +41,20 @@
class CreateDialog; class CreateDialog;
class EditorFileDialog; class EditorFileDialog;
class EditorValidationPanel;
class ScriptCreateDialog : public ConfirmationDialog { class ScriptCreateDialog : public ConfirmationDialog {
GDCLASS(ScriptCreateDialog, ConfirmationDialog); GDCLASS(ScriptCreateDialog, ConfirmationDialog);
enum {
MSG_ID_SCRIPT,
MSG_ID_PATH,
MSG_ID_BUILT_IN,
MSG_ID_TEMPLATE,
};
LineEdit *class_name = nullptr; LineEdit *class_name = nullptr;
Label *error_label = nullptr; EditorValidationPanel *validation_panel = 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;
LineEdit *parent_name = nullptr; LineEdit *parent_name = nullptr;
Button *parent_browse_button = nullptr; Button *parent_browse_button = nullptr;
Button *parent_search_button = nullptr; Button *parent_search_button = nullptr;
@ -67,6 +70,7 @@ class ScriptCreateDialog : public ConfirmationDialog {
AcceptDialog *alert = nullptr; AcceptDialog *alert = nullptr;
CreateDialog *select_class = nullptr; CreateDialog *select_class = nullptr;
bool is_browsing_parent = false; bool is_browsing_parent = false;
String path_error;
String template_inactive_message; String template_inactive_message;
String initial_bp; String initial_bp;
bool is_new_script_created = true; bool is_new_script_created = true;
@ -113,8 +117,6 @@ class ScriptCreateDialog : public ConfirmationDialog {
virtual void ok_pressed() override; virtual void ok_pressed() override;
void _create_new(); void _create_new();
void _load_exist(); 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_template_menu();
void _update_dialog(); void _update_dialog();
ScriptLanguage::ScriptTemplate _get_current_template() const; ScriptLanguage::ScriptTemplate _get_current_template() const;

View File

@ -33,6 +33,7 @@
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_validation_panel.h"
#include "scene/resources/shader_include.h" #include "scene/resources/shader_include.h"
#include "scene/resources/visual_shader.h" #include "scene/resources/visual_shader.h"
#include "servers/rendering/shader_types.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"))); 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() { void ShaderCreateDialog::_update_language_info() {
@ -147,7 +147,7 @@ void ShaderCreateDialog::ok_pressed() {
} }
is_new_shader_created = true; is_new_shader_created = true;
_update_dialog(); validation_panel->update();
} }
void ShaderCreateDialog::_create_new() { 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())); 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) { void ShaderCreateDialog::_built_in_toggled(bool p_enabled) {
@ -337,7 +337,7 @@ void ShaderCreateDialog::_built_in_toggled(bool p_enabled) {
} else { } else {
_path_changed(file_path->get_text()); _path_changed(file_path->get_text());
} }
_update_dialog(); validation_panel->update();
} }
void ShaderCreateDialog::_browse_path() { void ShaderCreateDialog::_browse_path() {
@ -378,10 +378,9 @@ void ShaderCreateDialog::_path_changed(const String &p_path) {
is_path_valid = false; is_path_valid = false;
is_new_shader_created = true; is_new_shader_created = true;
String path_error = _validate_path(p_path); path_error = _validate_path(p_path);
if (!path_error.is_empty()) { if (!path_error.is_empty()) {
_msg_path_valid(false, path_error); validation_panel->update();
_update_dialog();
return; return;
} }
@ -389,15 +388,16 @@ void ShaderCreateDialog::_path_changed(const String &p_path) {
String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges()); String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges());
if (f->file_exists(p)) { if (f->file_exists(p)) {
is_new_shader_created = false; is_new_shader_created = false;
_msg_path_valid(true, TTR("File exists, it will be reused."));
} }
is_path_valid = true; is_path_valid = true;
_update_dialog(); validation_panel->update();
} }
void ShaderCreateDialog::_path_submitted(const String &p_path) { 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) { 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 ""; 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() { void ShaderCreateDialog::_update_dialog() {
bool shader_ok = true;
if (!is_built_in && !is_path_valid) { if (!is_built_in && !is_path_valid) {
_msg_script_valid(false, TTR("Invalid path.")); validation_panel->set_message(MSG_ID_SHADER, TTR("Invalid path."), EditorValidationPanel::MSG_ERROR);
shader_ok = false;
} }
if (shader_ok) { if (!path_error.is_empty()) {
_msg_script_valid(true, TTR("Shader path/name is valid.")); 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) { if (!built_in_enabled) {
internal->set_pressed(false); internal->set_pressed(false);
@ -537,37 +518,23 @@ void ShaderCreateDialog::_update_dialog() {
internal->set_disabled(!built_in_enabled); 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) { if (is_built_in) {
set_ok_button_text(TTR("Create")); 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) { } else if (is_new_shader_created) {
set_ok_button_text(TTR("Create")); 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) { } else if (load_enabled) {
set_ok_button_text(TTR("Load")); set_ok_button_text(TTR("Load"));
if (is_path_valid) { 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 { } else {
set_ok_button_text(TTR("Create")); set_ok_button_text(TTR("Create"));
_msg_path_valid(false, TTR("Shader file already exists.")); validation_panel->set_message(MSG_ID_PATH, TTR("Shader file already exists."), EditorValidationPanel::MSG_ERROR);
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);
} }
} }
@ -588,35 +555,22 @@ ShaderCreateDialog::ShaderCreateDialog() {
// Error Fields. // Error Fields.
VBoxContainer *vb = memnew(VBoxContainer); validation_panel = memnew(EditorValidationPanel);
validation_panel->add_line(MSG_ID_SHADER, TTR("Shader path/name is valid."));
error_label = memnew(Label); validation_panel->add_line(MSG_ID_PATH, TTR("Will create a new shader file."));
vb->add_child(error_label); validation_panel->add_line(MSG_ID_BUILT_IN);
validation_panel->set_update_callback(callable_mp(this, &ShaderCreateDialog::_update_dialog));
path_error_label = memnew(Label); validation_panel->set_accept_button(get_ok_button());
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);
// Spacing. // Spacing.
Control *spacing = memnew(Control); Control *spacing = memnew(Control);
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
vb = memnew(VBoxContainer); VBoxContainer *vb = memnew(VBoxContainer);
vb->add_child(gc); vb->add_child(gc);
vb->add_child(spacing); vb->add_child(spacing);
vb->add_child(status_panel); vb->add_child(validation_panel);
add_child(vb); add_child(vb);
// Type. // Type.

View File

@ -40,10 +40,17 @@
#include "scene/gui/panel_container.h" #include "scene/gui/panel_container.h"
class EditorFileDialog; class EditorFileDialog;
class EditorValidationPanel;
class ShaderCreateDialog : public ConfirmationDialog { class ShaderCreateDialog : public ConfirmationDialog {
GDCLASS(ShaderCreateDialog, ConfirmationDialog); GDCLASS(ShaderCreateDialog, ConfirmationDialog);
enum {
MSG_ID_SHADER,
MSG_ID_PATH,
MSG_ID_BUILT_IN,
};
struct ShaderTypeData { struct ShaderTypeData {
List<String> extensions; List<String> extensions;
String default_extension; String default_extension;
@ -53,10 +60,7 @@ class ShaderCreateDialog : public ConfirmationDialog {
List<ShaderTypeData> type_data; List<ShaderTypeData> type_data;
GridContainer *gc = nullptr; GridContainer *gc = nullptr;
Label *error_label = nullptr; EditorValidationPanel *validation_panel = nullptr;
Label *path_error_label = nullptr;
Label *builtin_warning_label = nullptr;
PanelContainer *status_panel = nullptr;
OptionButton *type_menu = nullptr; OptionButton *type_menu = nullptr;
OptionButton *mode_menu = nullptr; OptionButton *mode_menu = nullptr;
OptionButton *template_menu = nullptr; OptionButton *template_menu = nullptr;
@ -67,6 +71,7 @@ class ShaderCreateDialog : public ConfirmationDialog {
AcceptDialog *alert = nullptr; AcceptDialog *alert = nullptr;
String initial_base_path; String initial_base_path;
String path_error;
bool is_new_shader_created = true; bool is_new_shader_created = true;
bool is_path_valid = false; bool is_path_valid = false;
bool is_built_in = false; bool is_built_in = false;
@ -93,8 +98,6 @@ class ShaderCreateDialog : public ConfirmationDialog {
virtual void ok_pressed() override; virtual void ok_pressed() override;
void _create_new(); void _create_new();
void _load_exist(); 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(); void _update_dialog();
protected: protected: