diff options
author | Yuri Roubinsky <chaosus89@gmail.com> | 2021-06-04 12:24:08 +0300 |
---|---|---|
committer | Yuri Roubinsky <chaosus89@gmail.com> | 2021-08-07 21:02:13 +0300 |
commit | 5e5cd2495d8ec8bc0685dbf3b98dc251c3c7bddb (patch) | |
tree | 1f039810d2dc0308bde14d75afc6d6dcd971c14a /editor | |
parent | 3177da1d053a6132668e12a8d8af82366f5c4ac9 (diff) |
Added editor dialog for easily creating shaders.
Diffstat (limited to 'editor')
-rw-r--r-- | editor/editor_properties.cpp | 4 | ||||
-rw-r--r-- | editor/editor_resource_picker.cpp | 41 | ||||
-rw-r--r-- | editor/editor_resource_picker.h | 19 | ||||
-rw-r--r-- | editor/plugins/visual_shader_editor_plugin.cpp | 1 | ||||
-rw-r--r-- | editor/scene_tree_dock.cpp | 59 | ||||
-rw-r--r-- | editor/scene_tree_dock.h | 9 | ||||
-rw-r--r-- | editor/shader_create_dialog.cpp | 643 | ||||
-rw-r--r-- | editor/shader_create_dialog.h | 115 |
8 files changed, 891 insertions, 0 deletions
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index fa03d281f2..789cabea9a 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2854,6 +2854,10 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const EditorScriptPicker *script_picker = memnew(EditorScriptPicker); script_picker->set_script_owner(Object::cast_to<Node>(p_object)); resource_picker = script_picker; + } else if (p_path == "shader" && p_base_type == "Shader" && Object::cast_to<ShaderMaterial>(p_object)) { + EditorShaderPicker *shader_picker = memnew(EditorShaderPicker); + shader_picker->set_edited_material(Object::cast_to<ShaderMaterial>(p_object)); + resource_picker = shader_picker; } else { resource_picker = memnew(EditorResourcePicker); } diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index f3965fe7de..a4ab749db4 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -847,6 +847,8 @@ EditorResourcePicker::EditorResourcePicker() { edit_button->connect("gui_input", callable_mp(this, &EditorResourcePicker::_button_input)); } +// EditorScriptPicker + void EditorScriptPicker::set_create_options(Object *p_menu_node) { PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node); if (!menu_node) { @@ -895,3 +897,42 @@ void EditorScriptPicker::_bind_methods() { EditorScriptPicker::EditorScriptPicker() { } + +// EditorShaderPicker + +void EditorShaderPicker::set_create_options(Object *p_menu_node) { + PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node); + if (!menu_node) { + return; + } + + menu_node->add_icon_item(get_theme_icon("Shader", "EditorIcons"), TTR("New Shader"), OBJ_MENU_NEW_SHADER); + menu_node->add_separator(); +} + +bool EditorShaderPicker::handle_menu_selected(int p_which) { + Ref<ShaderMaterial> material = Ref<ShaderMaterial>(get_edited_material()); + + switch (p_which) { + case OBJ_MENU_NEW_SHADER: { + if (material.is_valid()) { + EditorNode::get_singleton()->get_scene_tree_dock()->open_shader_dialog(material); + return true; + } + } break; + default: + break; + } + return false; +} + +void EditorShaderPicker::set_edited_material(ShaderMaterial *p_material) { + edited_material = p_material; +} + +ShaderMaterial *EditorShaderPicker::get_edited_material() const { + return edited_material; +} + +EditorShaderPicker::EditorShaderPicker() { +} diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index a4c3006c02..d77c31f831 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -144,4 +144,23 @@ public: EditorScriptPicker(); }; +class EditorShaderPicker : public EditorResourcePicker { + GDCLASS(EditorShaderPicker, EditorResourcePicker); + + enum ExtraMenuOption { + OBJ_MENU_NEW_SHADER = 10, + }; + + ShaderMaterial *edited_material = nullptr; + +public: + virtual void set_create_options(Object *p_menu_node) override; + virtual bool handle_menu_selected(int p_which) override; + + void set_edited_material(ShaderMaterial *p_material); + ShaderMaterial *get_edited_material() const; + + EditorShaderPicker(); +}; + #endif // EDITOR_RESOURCE_PICKER_H diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 452ad126b3..9a41a100bd 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1007,6 +1007,7 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { const Dictionary vs_version = visual_shader->get_engine_version(); if (!vs_version.has_all(components)) { visual_shader->update_engine_version(engine_version); + print_line(vformat(TTR("The shader (\"%s\") has been updated to correspond Godot %s.%s version."), visual_shader->get_path(), engine_version["major"], engine_version["minor"])); } else { for (int i = 0; i < components.size(); i++) { if (vs_version[components[i]] != engine_version[components[i]]) { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index c4d47c7594..8994adf112 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -45,6 +45,7 @@ #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" +#include "editor/shader_create_dialog.h" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" @@ -1939,12 +1940,31 @@ void SceneTreeDock::_script_created(Ref<Script> p_script) { _update_script_button(); } +void SceneTreeDock::_shader_created(Ref<Shader> p_shader) { + if (selected_shader_material.is_null()) { + return; + } + + Ref<Shader> existing = selected_shader_material->get_shader(); + + editor_data->get_undo_redo().create_action(TTR("Set Shader")); + editor_data->get_undo_redo().add_do_method(selected_shader_material.ptr(), "set_shader", p_shader); + editor_data->get_undo_redo().add_undo_method(selected_shader_material.ptr(), "set_shader", existing); + editor_data->get_undo_redo().commit_action(); +} + void SceneTreeDock::_script_creation_closed() { script_create_dialog->disconnect("script_created", callable_mp(this, &SceneTreeDock::_script_created)); script_create_dialog->disconnect("confirmed", callable_mp(this, &SceneTreeDock::_script_creation_closed)); script_create_dialog->disconnect("cancelled", callable_mp(this, &SceneTreeDock::_script_creation_closed)); } +void SceneTreeDock::_shader_creation_closed() { + shader_create_dialog->disconnect("shader_created", callable_mp(this, &SceneTreeDock::_shader_created)); + shader_create_dialog->disconnect("confirmed", callable_mp(this, &SceneTreeDock::_shader_creation_closed)); + shader_create_dialog->disconnect("cancelled", callable_mp(this, &SceneTreeDock::_shader_creation_closed)); +} + void SceneTreeDock::_toggle_editable_children_from_selection() { List<Node *> selection = editor_selection->get_selected_node_list(); List<Node *>::Element *e = selection.front(); @@ -2896,6 +2916,42 @@ void SceneTreeDock::open_script_dialog(Node *p_for_node, bool p_extend) { } } +void SceneTreeDock::attach_shader_to_selected() { + if (selected_shader_material.is_null()) { + return; + } + + String path = selected_shader_material->get_path(); + if (path == "") { + String root_path; + if (editor_data->get_edited_scene_root()) { + root_path = editor_data->get_edited_scene_root()->get_filename(); + } + String shader_name; + if (selected_shader_material->get_name().is_empty()) { + shader_name = root_path.get_file(); + } else { + shader_name = selected_shader_material->get_name(); + } + if (root_path == "") { + path = String("res://").plus_file(shader_name); + } else { + path = root_path.get_base_dir().plus_file(shader_name); + } + } + + shader_create_dialog->connect("shader_created", callable_mp(this, &SceneTreeDock::_shader_created)); + shader_create_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_shader_creation_closed)); + shader_create_dialog->connect("cancelled", callable_mp(this, &SceneTreeDock::_shader_creation_closed)); + shader_create_dialog->config(path); + shader_create_dialog->popup_centered(); +} + +void SceneTreeDock::open_shader_dialog(Ref<ShaderMaterial> &p_for_material) { + selected_shader_material = p_for_material; + attach_shader_to_selected(); +} + void SceneTreeDock::open_add_child_dialog() { create_dialog->set_base_type("CanvasItem"); _tool_selected(TOOL_NEW, true); @@ -3267,6 +3323,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel script_create_dialog->set_inheritance_base_type("Node"); add_child(script_create_dialog); + shader_create_dialog = memnew(ShaderCreateDialog); + add_child(shader_create_dialog); + reparent_dialog = memnew(ReparentDialog); add_child(reparent_dialog); reparent_dialog->connect("reparent", callable_mp(this, &SceneTreeDock::_node_reparent)); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 4952122cb7..ccdc0a3786 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -49,6 +49,7 @@ #include "scene_tree_editor.h" class EditorNode; +class ShaderCreateDialog; class SceneTreeDock : public VBoxContainer { GDCLASS(SceneTreeDock, VBoxContainer); @@ -138,6 +139,7 @@ class SceneTreeDock : public VBoxContainer { HashMap<String, Map<RES, RES>> clipboard_resource_remap; ScriptCreateDialog *script_create_dialog; + ShaderCreateDialog *shader_create_dialog; AcceptDialog *accept; ConfirmationDialog *delete_dialog; ConfirmationDialog *editable_instance_remove_dialog; @@ -166,6 +168,8 @@ class SceneTreeDock : public VBoxContainer { VBoxContainer *create_root_dialog; String selected_favorite_root; + Ref<ShaderMaterial> selected_shader_material; + void _add_children_to_popup(Object *p_obj, int p_depth); void _node_reparent(NodePath p_path, bool p_keep_global_xform); @@ -192,7 +196,9 @@ class SceneTreeDock : public VBoxContainer { void _node_selected(); void _node_renamed(); void _script_created(Ref<Script> p_script); + void _shader_created(Ref<Shader> p_shader); void _script_creation_closed(); + void _shader_creation_closed(); void _delete_confirm(bool p_cut = false); @@ -288,6 +294,9 @@ public: void attach_script_to_selected(bool p_extend); void open_script_dialog(Node *p_for_node, bool p_extend); + void attach_shader_to_selected(); + void open_shader_dialog(Ref<ShaderMaterial> &p_for_material); + void open_add_child_dialog(); void open_instance_child_dialog(); diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp new file mode 100644 index 0000000000..beae0c9c91 --- /dev/null +++ b/editor/shader_create_dialog.cpp @@ -0,0 +1,643 @@ +/*************************************************************************/ +/* shader_create_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "shader_create_dialog.h" +#include "editor/editor_scale.h" +#include "scene/resources/visual_shader.h" +#include "servers/rendering/shader_types.h" + +void ShaderCreateDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_theme(); + + String last_lang = EditorSettings::get_singleton()->get_project_metadata("shader_setup", "last_selected_language", ""); + if (!last_lang.is_empty()) { + for (int i = 0; i < language_menu->get_item_count(); i++) { + if (language_menu->get_item_text(i) == last_lang) { + language_menu->select(i); + current_language = i; + break; + } + } + } else { + language_menu->select(default_language); + } + + current_mode = EditorSettings::get_singleton()->get_project_metadata("shader_setup", "last_selected_mode", 0); + mode_menu->select(current_mode); + } break; + case NOTIFICATION_THEME_CHANGED: { + _update_theme(); + } break; + } +} + +void ShaderCreateDialog::_update_theme() { + Ref<Texture2D> shader_icon = gc->get_theme_icon(SNAME("Shader"), SNAME("EditorIcons")); + if (shader_icon.is_valid()) { + language_menu->set_item_icon(0, shader_icon); + } + + Ref<Texture2D> visual_shader_icon = gc->get_theme_icon(SNAME("VisualShader"), SNAME("EditorIcons")); + if (visual_shader_icon.is_valid()) { + language_menu->set_item_icon(1, visual_shader_icon); + } + + path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); + status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); +} + +void ShaderCreateDialog::_update_language_info() { + language_data.clear(); + + List<StringName> classes; + classes.push_front(SNAME("Shader")); + classes.push_front(SNAME("VisualShader")); + + for (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + language_data.push_back(ShaderTypeData()); + } + + int idx = 0; + for (List<ShaderTypeData>::Element *E = language_data.front(); E; E = E->next()) { + if (idx == int(SHADER_TYPE_TEXT)) { + E->get().use_templates = true; + E->get().extensions.push_back("gdshader"); + E->get().default_extension = "gdshader"; + } else { + E->get().default_extension = "tres"; + } + E->get().extensions.push_back("res"); + E->get().extensions.push_back("tres"); + idx++; + } +} + +void ShaderCreateDialog::_path_hbox_sorted() { + if (is_visible()) { + int filename_start_pos = initial_base_path.rfind("/") + 1; + int filename_end_pos = initial_base_path.length(); + + if (!is_built_in) { + file_path->select(filename_start_pos, filename_end_pos); + } + + file_path->set_caret_column(file_path->get_text().length()); + file_path->set_caret_column(filename_start_pos); + + file_path->grab_focus(); + } +} + +void ShaderCreateDialog::_mode_changed(int p_mode) { + current_mode = p_mode; + EditorSettings::get_singleton()->set_project_metadata("shader_setup", "last_selected_mode", p_mode); +} + +void ShaderCreateDialog::_template_changed(int p_template) { + current_template = p_template; + EditorSettings::get_singleton()->set_project_metadata("shader_setup", "last_selected_template", p_template); +} + +void ShaderCreateDialog::ok_pressed() { + if (is_new_shader_created) { + _create_new(); + } else { + _load_exist(); + } + + is_new_shader_created = true; + _update_dialog(); +} + +void ShaderCreateDialog::_create_new() { + RES shader; + + if (language_menu->get_selected() == int(SHADER_TYPE_TEXT)) { + Ref<Shader> text_shader; + text_shader.instantiate(); + shader = text_shader; + + StringBuilder code; + code += vformat("shader_type %s;\n", mode_menu->get_text().replace(" ", "").camelcase_to_underscore()); + + if (current_template == 0) { // Default template. + code += "\n"; + switch (current_mode) { + case Shader::MODE_SPATIAL: + code += "void fragment() {\n"; + code += "\t// Place fragment code here.\n"; + code += "}\n"; + break; + case Shader::MODE_CANVAS_ITEM: + code += "void fragment() {\n"; + code += "\t// Place fragment code here.\n"; + code += "}\n"; + break; + case Shader::MODE_PARTICLES: + code += "void start() {\n"; + code += "\t// Place start code here.\n"; + code += "}\n"; + code += "\n"; + code += "void process() {\n"; + code += "\t// Place process code here.\n"; + code += "}\n"; + break; + case Shader::MODE_SKY: + code += "void sky() {\n"; + code += "\t// Place sky code here.\n"; + code += "}\n"; + break; + } + } + text_shader->set_code(code.as_string()); + } else { + Ref<VisualShader> visual_shader; + visual_shader.instantiate(); + shader = visual_shader; + visual_shader->set_engine_version(Engine::get_singleton()->get_version_info()); + visual_shader->set_mode(Shader::Mode(current_mode)); + } + + if (!is_built_in) { + String lpath = ProjectSettings::get_singleton()->localize_path(file_path->get_text()); + shader->set_path(lpath); + Error err = ResourceSaver::save(lpath, shader, ResourceSaver::FLAG_CHANGE_PATH); + if (err != OK) { + alert->set_text(TTR("Error - Could not create shader in filesystem.")); + alert->popup_centered(); + return; + } + } + + emit_signal("shader_created", shader); + hide(); +} + +void ShaderCreateDialog::_load_exist() { + String path = file_path->get_text(); + RES p_shader = ResourceLoader::load(path, "Shader"); + if (p_shader.is_null()) { + alert->set_text(vformat(TTR("Error loading shader from %s"), path)); + alert->popup_centered(); + return; + } + + emit_signal("shader_created", p_shader); + hide(); +} + +void ShaderCreateDialog::_language_changed(int p_language) { + ShaderTypeData data = language_data[p_language]; + + String selected_ext = "." + data.default_extension; + String path = file_path->get_text(); + String extension = ""; + + if (path != "") { + if (path.find(".") != -1) { + extension = path.get_extension(); + } + if (extension.length() == 0) { + path += selected_ext; + } else { + path = path.get_basename() + selected_ext; + } + } else { + path = "shader" + selected_ext; + } + _path_changed(path); + file_path->set_text(path); + + template_menu->set_disabled(!data.use_templates); + template_menu->clear(); + + if (data.use_templates) { + int last_template = EditorSettings::get_singleton()->get_project_metadata("shader_setup", "last_selected_template", 0); + + template_menu->add_item(TTR("Default")); + template_menu->add_item(TTR("Empty")); + + template_menu->select(last_template); + current_template = last_template; + } else { + template_menu->add_item(TTR("N/A")); + } + + EditorSettings::get_singleton()->set_project_metadata("shader_setup", "last_selected_language", language_menu->get_item_text(language_menu->get_selected())); + _update_dialog(); +} + +void ShaderCreateDialog::_built_in_toggled(bool p_enabled) { + is_built_in = p_enabled; + if (p_enabled) { + is_new_shader_created = true; + } else { + _path_changed(file_path->get_text()); + } + _update_dialog(); +} + +void ShaderCreateDialog::_browse_path() { + file_browse->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_browse->set_title(TTR("Open Shader / Choose Location")); + file_browse->get_ok_button()->set_text(TTR("Open")); + + file_browse->set_disable_overwrite_warning(true); + file_browse->clear_filters(); + + List<String> extensions = language_data[language_menu->get_selected()].extensions; + + for (const String &E : extensions) { + file_browse->add_filter("*." + E); + } + + file_browse->set_current_path(file_path->get_text()); + file_browse->popup_file_dialog(); +} + +void ShaderCreateDialog::_file_selected(const String &p_file) { + String p = ProjectSettings::get_singleton()->localize_path(p_file); + file_path->set_text(p); + _path_changed(p); + + String filename = p.get_file().get_basename(); + int select_start = p.rfind(filename); + file_path->select(select_start, select_start + filename.length()); + file_path->set_caret_column(select_start + filename.length()); + file_path->grab_focus(); +} + +void ShaderCreateDialog::_path_changed(const String &p_path) { + if (is_built_in) { + return; + } + + is_path_valid = false; + is_new_shader_created = true; + + String path_error = _validate_path(p_path, false); + if (path_error != "") { + _msg_path_valid(false, path_error); + _update_dialog(); + return; + } + + DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES); + 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.")); + } + memdelete(f); + + is_path_valid = true; + _update_dialog(); +} + +void ShaderCreateDialog::_path_submitted(const String &p_path) { + ok_pressed(); +} + +void ShaderCreateDialog::config(const String &p_base_path, bool p_built_in_enabled, bool p_load_enabled) { + if (p_base_path != "") { + initial_base_path = p_base_path.get_basename(); + file_path->set_text(initial_base_path + "." + language_data[language_menu->get_selected()].default_extension); + current_language = language_menu->get_selected(); + } else { + initial_base_path = ""; + file_path->set_text(""); + } + file_path->deselect(); + + built_in_enabled = p_built_in_enabled; + load_enabled = p_load_enabled; + + _language_changed(current_language); + _path_changed(file_path->get_text()); +} + +String ShaderCreateDialog::_validate_path(const String &p_path, bool p_file_must_exist) { + String p = p_path.strip_edges(); + + if (p == "") { + return TTR("Path is empty."); + } + if (p.get_file().get_basename() == "") { + return TTR("Filename is empty."); + } + + p = ProjectSettings::get_singleton()->localize_path(p); + if (!p.begins_with("res://")) { + return TTR("Path is not local."); + } + + DirAccess *d = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (d->change_dir(p.get_base_dir()) != OK) { + memdelete(d); + return TTR("Invalid base path."); + } + memdelete(d); + + DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (f->dir_exists(p)) { + memdelete(f); + return TTR("A directory with the same name exists."); + } else if (p_file_must_exist && !f->file_exists(p)) { + memdelete(f); + return TTR("File does not exist."); + } + memdelete(f); + + String extension = p.get_extension(); + List<String> extensions; + + for (int l = 0; l < SHADER_TYPE_MAX; l++) { + for (List<String>::Element *E = language_data[l].extensions.front(); E; E = E->next()) { + extensions.push_back(E->get()); + } + } + + ShaderTypeData data = language_data[language_menu->get_selected()]; + + bool found = false; + bool match = false; + int index = 0; + for (List<String>::Element *E = extensions.front(); E; E = E->next()) { + if (E->get().nocasecmp_to(extension) == 0) { + found = true; + if (E->get() == data.default_extension) { + match = true; + } + break; + } + index++; + } + + if (!found) { + return TTR("Invalid extension."); + } + if (!match) { + return TTR("Wrong extension chosen."); + } + + String path_error = ScriptServer::get_language(language_menu->get_selected())->validate_path(p); + if (path_error != "") { + return path_error; + } + + return ""; +} + +void ShaderCreateDialog::_msg_script_valid(bool valid, const String &p_msg) { + error_label->set_text("- " + p_msg); + if (valid) { + error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor")); + } else { + error_label->add_theme_color_override("font_color", gc->get_theme_color("error_color", "Editor")); + } +} + +void ShaderCreateDialog::_msg_path_valid(bool valid, const String &p_msg) { + path_error_label->set_text("- " + p_msg); + if (valid) { + path_error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor")); + } else { + path_error_label->add_theme_color_override("font_color", gc->get_theme_color("error_color", "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; + } + if (shader_ok) { + _msg_script_valid(true, TTR("Shader path/name is valid.")); + } + if (!built_in_enabled) { + internal->set_pressed(false); + } + + if (is_built_in) { + file_path->set_editable(false); + path_button->set_disabled(true); + re_check_path = true; + } else { + file_path->set_editable(true); + path_button->set_disabled(false); + if (re_check_path) { + re_check_path = false; + _path_changed(file_path->get_text()); + } + } + + internal->set_disabled(!built_in_enabled); + + builtin_warning_label->set_visible(is_built_in); + + if (is_built_in) { + get_ok_button()->set_text(TTR("Create")); + _msg_path_valid(true, TTR("Built-in script (into scene file).")); + } else if (is_new_shader_created) { + get_ok_button()->set_text(TTR("Create")); + if (is_path_valid) { + _msg_path_valid(true, TTR("Will create a new shader file.")); + } + } else if (load_enabled) { + get_ok_button()->set_text(TTR("Load")); + if (is_path_valid) { + _msg_path_valid(true, TTR("Will load an existing shader file.")); + } + } else { + get_ok_button()->set_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); + } +} + +void ShaderCreateDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("config", "path", "built_in_enabled", "load_enabled"), &ShaderCreateDialog::config, DEFVAL(true), DEFVAL(true)); + + ADD_SIGNAL(MethodInfo("shader_created", PropertyInfo(Variant::OBJECT, "shader", PROPERTY_HINT_RESOURCE_TYPE, "Shader"))); +} + +ShaderCreateDialog::ShaderCreateDialog() { + _update_language_info(); + + // Main Controls. + + gc = memnew(GridContainer); + gc->set_columns(2); + + // 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(Label::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. + + Control *spacing = memnew(Control); + spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); + + vb = memnew(VBoxContainer); + vb->add_child(gc); + vb->add_child(spacing); + vb->add_child(status_panel); + add_child(vb); + + // Language. + + language_menu = memnew(OptionButton); + language_menu->set_custom_minimum_size(Size2(250, 0) * EDSCALE); + language_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL); + gc->add_child(memnew(Label(TTR("Language:")))); + gc->add_child(language_menu); + + for (int i = 0; i < SHADER_TYPE_MAX; i++) { + String language; + bool invalid = false; + switch (i) { + case SHADER_TYPE_TEXT: + language = "Shader"; + default_language = i; + break; + case SHADER_TYPE_VISUAL: + language = "VisualShader"; + break; + case SHADER_TYPE_MAX: + invalid = true; + break; + default: + invalid = true; + break; + } + if (invalid) { + continue; + } + language_menu->add_item(language); + } + if (default_language >= 0) { + language_menu->select(default_language); + } + current_language = default_language; + language_menu->connect("item_selected", callable_mp(this, &ShaderCreateDialog::_language_changed)); + + // Modes. + + mode_menu = memnew(OptionButton); + for (const List<String>::Element *E = ShaderTypes::get_singleton()->get_types_list().front(); E; E = E->next()) { + mode_menu->add_item(E->get().capitalize()); + } + gc->add_child(memnew(Label(TTR("Mode:")))); + gc->add_child(mode_menu); + mode_menu->connect("item_selected", callable_mp(this, &ShaderCreateDialog::_mode_changed)); + + // Templates. + + template_menu = memnew(OptionButton); + gc->add_child(memnew(Label(TTR("Template:")))); + gc->add_child(template_menu); + template_menu->connect("item_selected", callable_mp(this, &ShaderCreateDialog::_template_changed)); + + // Built-in Shader. + + internal = memnew(CheckBox); + internal->set_text(TTR("On")); + internal->connect("toggled", callable_mp(this, &ShaderCreateDialog::_built_in_toggled)); + gc->add_child(memnew(Label(TTR("Built-in Shader:")))); + gc->add_child(internal); + + // Path. + + HBoxContainer *hb = memnew(HBoxContainer); + hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hb->connect("sort_children", callable_mp(this, &ShaderCreateDialog::_path_hbox_sorted)); + file_path = memnew(LineEdit); + file_path->connect("text_changed", callable_mp(this, &ShaderCreateDialog::_path_changed)); + file_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hb->add_child(file_path); + path_button = memnew(Button); + path_button->connect("pressed", callable_mp(this, &ShaderCreateDialog::_browse_path)); + hb->add_child(path_button); + gc->add_child(memnew(Label(TTR("Path:")))); + gc->add_child(hb); + + // Dialog Setup. + + file_browse = memnew(EditorFileDialog); + file_browse->connect("file_selected", callable_mp(this, &ShaderCreateDialog::_file_selected)); + file_browse->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + add_child(file_browse); + + alert = memnew(AcceptDialog); + alert->get_label()->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + alert->get_label()->set_align(Label::ALIGN_CENTER); + alert->get_label()->set_valign(Label::VALIGN_CENTER); + alert->get_label()->set_custom_minimum_size(Size2(325, 60) * EDSCALE); + add_child(alert); + + get_ok_button()->set_text(TTR("Create")); + set_hide_on_ok(false); + + set_title(TTR("Create Shader")); +} diff --git a/editor/shader_create_dialog.h b/editor/shader_create_dialog.h new file mode 100644 index 0000000000..cc338cd6b5 --- /dev/null +++ b/editor/shader_create_dialog.h @@ -0,0 +1,115 @@ +/*************************************************************************/ +/* shader_create_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 SHADER_CREATE_DIALOG_H +#define SHADER_CREATE_DIALOG_H + +#include "editor/editor_file_dialog.h" +#include "editor/editor_settings.h" +#include "scene/gui/check_box.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" + +class ShaderCreateDialog : public ConfirmationDialog { + GDCLASS(ShaderCreateDialog, ConfirmationDialog); + + enum ShaderType { + SHADER_TYPE_TEXT, + SHADER_TYPE_VISUAL, + SHADER_TYPE_MAX, + }; + + struct ShaderTypeData { + List<String> extensions; + String default_extension; + bool use_templates = false; + }; + + List<ShaderTypeData> language_data; + + GridContainer *gc = nullptr; + Label *error_label = nullptr; + Label *path_error_label = nullptr; + Label *builtin_warning_label = nullptr; + PanelContainer *status_panel = nullptr; + OptionButton *language_menu = nullptr; + OptionButton *mode_menu = nullptr; + OptionButton *template_menu = nullptr; + CheckBox *internal = nullptr; + LineEdit *file_path = nullptr; + Button *path_button = nullptr; + EditorFileDialog *file_browse = nullptr; + AcceptDialog *alert = nullptr; + + String initial_base_path; + bool is_new_shader_created = true; + bool is_path_valid = false; + bool is_built_in = false; + bool built_in_enabled = true; + bool load_enabled = false; + bool re_check_path = false; + int current_language = -1; + int default_language = -1; + int current_mode = 0; + int current_template = 0; + + virtual void _update_language_info(); + + void _path_hbox_sorted(); + void _path_changed(const String &p_path = String()); + void _path_submitted(const String &p_path = String()); + void _language_changed(int p_language = 0); + void _built_in_toggled(bool p_enabled); + void _template_changed(int p_template = 0); + void _mode_changed(int p_mode = 0); + void _browse_path(); + void _file_selected(const String &p_file); + String _validate_path(const String &p_path, bool p_file_must_exist); + 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: + void _update_theme(); + void _notification(int p_what); + static void _bind_methods(); + +public: + void config(const String &p_base_path, bool p_built_in_enabled = true, bool p_load_enabled = true); + ShaderCreateDialog(); +}; + +#endif |