diff options
Diffstat (limited to 'editor')
32 files changed, 2648 insertions, 591 deletions
diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp index bea95d873e..fc47e1ef5c 100644 --- a/editor/audio_stream_preview.cpp +++ b/editor/audio_stream_preview.cpp @@ -153,6 +153,8 @@ void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) { singleton->call_deferred(SNAME("_update_emit"), preview->id); } + preview->preview->version++; + preview->playback->stop(); preview->generating.clear(); @@ -171,7 +173,7 @@ Ref<AudioStreamPreview> AudioStreamPreviewGenerator::generate_preview(const Ref< Preview *preview = &previews[p_stream->get_instance_id()]; preview->base_stream = p_stream; - preview->playback = preview->base_stream->instance_playback(); + preview->playback = preview->base_stream->instantiate_playback(); preview->generating.set(); preview->id = p_stream->get_instance_id(); diff --git a/editor/audio_stream_preview.h b/editor/audio_stream_preview.h index 307dd93b34..0e3c8f70d2 100644 --- a/editor/audio_stream_preview.h +++ b/editor/audio_stream_preview.h @@ -43,8 +43,10 @@ class AudioStreamPreview : public RefCounted { float length; friend class AudioStreamPreviewGenerator; + uint64_t version = 1; public: + uint64_t get_version() const { return version; } float get_length() const; float get_max(float p_time, float p_time_next) const; float get_min(float p_time, float p_time_next) const; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 3a0edf301d..fd331503ca 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1594,6 +1594,10 @@ void CodeTextEditor::set_error_pos(int p_line, int p_column) { error_column = p_column; } +Point2i CodeTextEditor::get_error_pos() const { + return Point2i(error_line, error_column); +} + void CodeTextEditor::goto_error() { if (!error->get_text().is_empty()) { if (text_editor->get_line_count() != error_line) { diff --git a/editor/code_editor.h b/editor/code_editor.h index e2441cec2b..49679cc700 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -253,13 +253,14 @@ public: void update_editor_settings(); void set_error(const String &p_error); void set_error_pos(int p_line, int p_column); + Point2i get_error_pos() const; void update_line_and_column() { _line_col_changed(); } CodeEdit *get_text_editor() { return text_editor; } FindReplaceBar *get_find_replace_bar() { return find_replace_bar; } void set_find_replace_bar(FindReplaceBar *p_bar); void remove_find_replace_bar(); virtual void apply_code() {} - void goto_error(); + virtual void goto_error(); void toggle_bookmark(); void goto_next_bookmark(); diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp index 3d4bee4b4e..8fa486408e 100644 --- a/editor/editor_asset_installer.cpp +++ b/editor/editor_asset_installer.cpp @@ -112,6 +112,7 @@ void EditorAssetInstaller::open(const String &p_path, int p_depth) { extension_guess["glb"] = tree->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")); extension_guess["gdshader"] = tree->get_theme_icon(SNAME("Shader"), SNAME("EditorIcons")); + extension_guess["gdshaderinc"] = tree->get_theme_icon(SNAME("TextFile"), SNAME("EditorIcons")); extension_guess["gd"] = tree->get_theme_icon(SNAME("GDScript"), SNAME("EditorIcons")); if (Engine::get_singleton()->has_singleton("GodotSharp")) { extension_guess["cs"] = tree->get_theme_icon(SNAME("CSharpScript"), SNAME("EditorIcons")); diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp new file mode 100644 index 0000000000..6a5604290f --- /dev/null +++ b/editor/editor_build_profile.cpp @@ -0,0 +1,797 @@ +/*************************************************************************/ +/* editor_build_profile.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "editor_build_profile.h" + +#include "core/io/dir_access.h" +#include "core/io/json.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +const char *EditorBuildProfile::build_option_identifiers[BUILD_OPTION_MAX] = { + // This maps to SCons build options. + "disable_3d", + "disable_2d_physics", + "disable_3d_physics", + "disable_navigation", + "openxr", + "opengl3", + "vulkan", +}; + +const bool EditorBuildProfile::build_option_disable_values[BUILD_OPTION_MAX] = { + // This maps to SCons build options. + true, + true, + true, + true, + false, + false, + false +}; + +void EditorBuildProfile::set_disable_class(const StringName &p_class, bool p_disabled) { + if (p_disabled) { + disabled_classes.insert(p_class); + } else { + disabled_classes.erase(p_class); + } +} + +bool EditorBuildProfile::is_class_disabled(const StringName &p_class) const { + if (p_class == StringName()) { + return false; + } + return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class)); +} + +void EditorBuildProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) { + if (p_collapsed) { + collapsed_classes.insert(p_class); + } else { + collapsed_classes.erase(p_class); + } +} + +bool EditorBuildProfile::is_item_collapsed(const StringName &p_class) const { + return collapsed_classes.has(p_class); +} + +void EditorBuildProfile::set_disable_build_option(BuildOption p_build_option, bool p_disable) { + ERR_FAIL_INDEX(p_build_option, BUILD_OPTION_MAX); + build_options_disabled[p_build_option] = p_disable; +} + +void EditorBuildProfile::clear_disabled_classes() { + disabled_classes.clear(); + collapsed_classes.clear(); +} + +bool EditorBuildProfile::is_build_option_disabled(BuildOption p_build_option) const { + ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false); + return build_options_disabled[p_build_option]; +} + +bool EditorBuildProfile::get_build_option_disable_value(BuildOption p_build_option) { + ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false); + return build_option_disable_values[p_build_option]; +} + +void EditorBuildProfile::set_force_detect_classes(const String &p_classes) { + force_detect_classes = p_classes; +} + +String EditorBuildProfile::get_force_detect_classes() const { + return force_detect_classes; +} + +String EditorBuildProfile::get_build_option_name(BuildOption p_build_option) { + ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String()); + const char *build_option_names[BUILD_OPTION_MAX] = { + TTRC("3D Engine"), + TTRC("2D Physics"), + TTRC("3D Physics"), + TTRC("Navigation"), + TTRC("XR"), + TTRC("RenderingDevice"), + TTRC("OpenGL"), + TTRC("Vulkan"), + }; + return TTRGET(build_option_names[p_build_option]); +} + +String EditorBuildProfile::get_build_option_description(BuildOption p_build_option) { + ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String()); + + const char *build_option_descriptions[BUILD_OPTION_MAX] = { + TTRC("3D Nodes as well as RenderingServer access to 3D features."), + TTRC("2D Physics nodes and PhysicsServer2D."), + TTRC("3D Physics nodes and PhysicsServer3D."), + TTRC("Navigation, both 2D and 3D."), + TTRC("XR (AR and VR)."), + TTRC("RenderingDevice based rendering (if disabled, the OpenGL back-end is required)."), + TTRC("OpenGL back-end (if disabled, the RenderingDevice back-end is required)."), + TTRC("Vulkan back-end of RenderingDevice."), + }; + + return TTRGET(build_option_descriptions[p_build_option]); +} + +Error EditorBuildProfile::save_to_file(const String &p_path) { + Dictionary data; + data["type"] = "build_profile"; + Array dis_classes; + for (const StringName &E : disabled_classes) { + dis_classes.push_back(String(E)); + } + dis_classes.sort(); + data["disabled_classes"] = dis_classes; + + Dictionary dis_build_options; + for (int i = 0; i < BUILD_OPTION_MAX; i++) { + if (build_options_disabled[i]) { + dis_build_options[build_option_identifiers[i]] = build_option_disable_values[i]; + } + } + + data["disabled_build_options"] = dis_build_options; + + if (!force_detect_classes.is_empty()) { + data["force_detect_classes"] = force_detect_classes; + } + + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); + + JSON json; + String text = json.stringify(data, "\t"); + f->store_string(text); + return OK; +} + +Error EditorBuildProfile::load_from_file(const String &p_path) { + Error err; + String text = FileAccess::get_file_as_string(p_path, &err); + if (err != OK) { + return err; + } + + JSON json; + err = json.parse(text); + if (err != OK) { + ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message()); + return ERR_PARSE_ERROR; + } + + Dictionary data = json.get_data(); + + if (!data.has("type") || String(data["type"]) != "build_profile") { + ERR_PRINT("Error parsing '" + p_path + "', it's not a build profile."); + return ERR_PARSE_ERROR; + } + + disabled_classes.clear(); + + if (data.has("disabled_classes")) { + Array disabled_classes_arr = data["disabled_classes"]; + for (int i = 0; i < disabled_classes_arr.size(); i++) { + disabled_classes.insert(disabled_classes_arr[i]); + } + } + + for (int i = 0; i < BUILD_OPTION_MAX; i++) { + build_options_disabled[i] = false; + } + + if (data.has("disabled_build_options")) { + Dictionary disabled_build_options_arr = data["disabled_build_options"]; + List<Variant> keys; + disabled_build_options_arr.get_key_list(&keys); + + for (const Variant &K : keys) { + String key = K; + + for (int i = 0; i < BUILD_OPTION_MAX; i++) { + String f = build_option_identifiers[i]; + if (f == key) { + build_options_disabled[i] = true; + break; + } + } + } + } + + if (data.has("force_detect_classes")) { + force_detect_classes = data["force_detect_classes"]; + } + + return OK; +} + +void EditorBuildProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorBuildProfile::set_disable_class); + ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorBuildProfile::is_class_disabled); + + ClassDB::bind_method(D_METHOD("set_disable_build_option", "build_option", "disable"), &EditorBuildProfile::set_disable_build_option); + ClassDB::bind_method(D_METHOD("is_build_option_disabled", "build_option"), &EditorBuildProfile::is_build_option_disabled); + + ClassDB::bind_method(D_METHOD("get_build_option_name", "build_option"), &EditorBuildProfile::_get_build_option_name); + + ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorBuildProfile::save_to_file); + ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorBuildProfile::load_from_file); + + BIND_ENUM_CONSTANT(BUILD_OPTION_3D); + BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_2D); + BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_3D); + BIND_ENUM_CONSTANT(BUILD_OPTION_NAVIGATION); + BIND_ENUM_CONSTANT(BUILD_OPTION_XR); + BIND_ENUM_CONSTANT(BUILD_OPTION_RENDERING_DEVICE); + BIND_ENUM_CONSTANT(BUILD_OPTION_OPENGL); + BIND_ENUM_CONSTANT(BUILD_OPTION_VULKAN); + BIND_ENUM_CONSTANT(BUILD_OPTION_MAX); +} + +EditorBuildProfile::EditorBuildProfile() {} + +////////////////////////// + +void EditorBuildProfileManager::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + String last_file = EditorSettings::get_singleton()->get_project_metadata("build_profile", "last_file_path", ""); + if (!last_file.is_empty()) { + _import_profile(last_file); + } + if (edited.is_null()) { + edited.instantiate(); + _update_edited_profile(); + } + + } break; + } +} + +void EditorBuildProfileManager::_profile_action(int p_action) { + last_action = Action(p_action); + + switch (p_action) { + case ACTION_RESET: { + confirm_dialog->set_text("Reset the edited profile?"); + confirm_dialog->popup_centered(); + } break; + case ACTION_LOAD: { + import_profile->popup_file_dialog(); + } break; + case ACTION_SAVE: { + if (!profile_path->get_text().is_empty()) { + Error err = edited->save_to_file(profile_path->get_text()); + if (err != OK) { + EditorNode::get_singleton()->show_warning(TTR("File saving failed.")); + } + break; + } + [[fallthrough]]; + } + case ACTION_SAVE_AS: { + export_profile->popup_file_dialog(); + export_profile->set_current_file(profile_path->get_text()); + } break; + case ACTION_NEW: { + confirm_dialog->set_text("Create a new profile?"); + confirm_dialog->popup_centered(); + } break; + case ACTION_DETECT: { + confirm_dialog->set_text("This will scan all files in the current project to detect used classes."); + confirm_dialog->popup_centered(); + } break; + case ACTION_MAX: { + } break; + } +} + +void EditorBuildProfileManager::_find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected) { + if (p_dir == nullptr) { + return; + } + + for (int i = 0; i < p_dir->get_file_count(); i++) { + String p = p_dir->get_file_path(i); + + uint64_t timestamp = 0; + String md5; + + if (p_cache.has(p)) { + const DetectedFile &cache = p_cache[p]; + // Check if timestamp and MD5 match. + timestamp = FileAccess::get_modified_time(p); + bool cache_valid = true; + if (cache.timestamp != timestamp) { + md5 = FileAccess::get_md5(p); + if (md5 != cache.md5) { + cache_valid = false; + } + } + + if (cache_valid) { + r_detected.insert(p, cache); + continue; + } + } + + // Not cached, or cache invalid. + + DetectedFile cache; + + HashSet<StringName> classes; + ResourceLoader::get_classes_used(p, &classes); + + for (const StringName &E : classes) { + cache.classes.push_back(E); + } + + if (md5.is_empty()) { + cache.timestamp = FileAccess::get_modified_time(p); + cache.md5 = FileAccess::get_md5(p); + } else { + cache.timestamp = timestamp; + cache.md5 = md5; + } + + r_detected.insert(p, cache); + } + + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _find_files(p_dir->get_subdir(i), p_cache, r_detected); + } +} + +void EditorBuildProfileManager::_detect_classes() { + HashMap<String, DetectedFile> previous_file_cache; + + Ref<FileAccess> f = FileAccess::open("res://.godot/editor/used_class_cache", FileAccess::READ); + if (f.is_valid()) { + while (!f->eof_reached()) { + String l = f->get_line(); + Vector<String> fields = l.split("::"); + if (fields.size() == 4) { + String path = fields[0]; + DetectedFile df; + df.timestamp = fields[1].to_int(); + df.md5 = fields[2]; + df.classes = fields[3].split(","); + previous_file_cache.insert(path, df); + } + } + f.unref(); + } + + HashMap<String, DetectedFile> updated_file_cache; + + _find_files(EditorFileSystem::get_singleton()->get_filesystem(), previous_file_cache, updated_file_cache); + + HashSet<StringName> used_classes; + + // Find classes and update the disk cache in the process. + f = FileAccess::open("res://.godot/editor/used_class_cache", FileAccess::WRITE); + + for (const KeyValue<String, DetectedFile> &E : updated_file_cache) { + String l = E.key + "::" + itos(E.value.timestamp) + "::" + E.value.md5 + "::"; + for (int i = 0; i < E.value.classes.size(); i++) { + String c = E.value.classes[i]; + if (i > 0) { + l += ","; + } + l += c; + used_classes.insert(c); + } + f->store_line(l); + } + + f.unref(); + + // Add forced ones. + + Vector<String> force_detect = edited->get_force_detect_classes().split(","); + for (int i = 0; i < force_detect.size(); i++) { + String c = force_detect[i].strip_edges(); + if (c.is_empty()) { + continue; + } + used_classes.insert(c); + } + + // Filter all classes to discard inherited ones. + + HashSet<StringName> all_used_classes; + + for (const StringName &E : used_classes) { + StringName c = E; + if (!ClassDB::class_exists(c)) { + // Maybe this is an old class that got replaced? try getting compat class. + c = ClassDB::get_compatibility_class(c); + if (!c) { + // No luck, skip. + continue; + } + } + while (c) { + all_used_classes.insert(c); + c = ClassDB::get_parent_class(c); + } + } + + edited->clear_disabled_classes(); + + List<StringName> all_classes; + ClassDB::get_class_list(&all_classes); + + for (const StringName &E : all_classes) { + if (all_used_classes.has(E)) { + // This class is valid, do nothing. + continue; + } + + StringName p = ClassDB::get_parent_class(E); + if (!p || all_used_classes.has(p)) { + // If no parent, or if the parent is enabled, then add to disabled classes. + // This way we avoid disabling redundant classes. + edited->set_disable_class(E, true); + } + } +} + +void EditorBuildProfileManager::_action_confirm() { + switch (last_action) { + case ACTION_RESET: { + edited.instantiate(); + _update_edited_profile(); + } break; + case ACTION_LOAD: { + } break; + case ACTION_SAVE: { + } break; + case ACTION_SAVE_AS: { + } break; + case ACTION_NEW: { + profile_path->set_text(""); + edited.instantiate(); + _update_edited_profile(); + } break; + case ACTION_DETECT: { + _detect_classes(); + _update_edited_profile(); + } break; + case ACTION_MAX: { + } break; + } +} + +void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { + TreeItem *class_item = class_list->create_item(p_parent); + class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class, "Node")); + String text = p_class; + + bool disabled = edited->is_class_disabled(p_class); + if (disabled) { + class_item->set_custom_color(0, class_list->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); + } + + class_item->set_text(0, text); + class_item->set_editable(0, true); + class_item->set_selectable(0, true); + class_item->set_metadata(0, p_class); + + bool collapsed = edited->is_item_collapsed(p_class); + class_item->set_collapsed(collapsed); + + if (p_class == p_selected) { + class_item->select(0); + } + if (disabled) { + // Class disabled, do nothing else (do not show further). + return; + } + + class_item->set_checked(0, true); // If it's not disabled, its checked. + + List<StringName> child_classes; + ClassDB::get_direct_inheriters_from_class(p_class, &child_classes); + child_classes.sort_custom<StringName::AlphCompare>(); + + for (const StringName &name : child_classes) { + if (String(name).begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) { + continue; + } + _fill_classes_from(class_item, name, p_selected); + } +} + +void EditorBuildProfileManager::_class_list_item_selected() { + if (updating_build_options) { + return; + } + + TreeItem *item = class_list->get_selected(); + if (!item) { + return; + } + + Variant md = item->get_metadata(0); + if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + String class_name = md; + String class_description; + + DocTools *dd = EditorHelp::get_doc_data(); + HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name); + if (E) { + class_description = DTR(E->value.brief_description); + } + + description_bit->set_text(class_description); + } else if (md.get_type() == Variant::INT) { + int build_option_id = md; + String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption(build_option_id)); + + description_bit->set_text(TTRGET(build_option_description)); + return; + } else { + return; + } +} + +void EditorBuildProfileManager::_class_list_item_edited() { + if (updating_build_options) { + return; + } + + TreeItem *item = class_list->get_edited(); + if (!item) { + return; + } + + bool checked = item->is_checked(0); + + Variant md = item->get_metadata(0); + if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + String class_selected = md; + edited->set_disable_class(class_selected, !checked); + _update_edited_profile(); + } else if (md.get_type() == Variant::INT) { + int build_option_selected = md; + edited->set_disable_build_option(EditorBuildProfile::BuildOption(build_option_selected), !checked); + } +} + +void EditorBuildProfileManager::_class_list_item_collapsed(Object *p_item) { + if (updating_build_options) { + return; + } + + TreeItem *item = Object::cast_to<TreeItem>(p_item); + if (!item) { + return; + } + + Variant md = item->get_metadata(0); + if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) { + return; + } + + String class_name = md; + bool collapsed = item->is_collapsed(); + edited->set_item_collapsed(class_name, collapsed); +} + +void EditorBuildProfileManager::_update_edited_profile() { + String class_selected; + int build_option_selected = -1; + + if (class_list->get_selected()) { + Variant md = class_list->get_selected()->get_metadata(0); + if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + class_selected = md; + } else if (md.get_type() == Variant::INT) { + build_option_selected = md; + } + } + + class_list->clear(); + + updating_build_options = true; + + TreeItem *root = class_list->create_item(); + + TreeItem *build_options = class_list->create_item(root); + build_options->set_text(0, TTR("General Features:")); + for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_MAX; i++) { + TreeItem *build_option; + build_option = class_list->create_item(build_options); + + build_option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + build_option->set_text(0, EditorBuildProfile::get_build_option_name(EditorBuildProfile::BuildOption(i))); + build_option->set_selectable(0, true); + build_option->set_editable(0, true); + build_option->set_metadata(0, i); + if (!edited->is_build_option_disabled(EditorBuildProfile::BuildOption(i))) { + build_option->set_checked(0, true); + } + + if (i == build_option_selected) { + build_option->select(0); + } + } + + TreeItem *classes = class_list->create_item(root); + classes->set_text(0, TTR("Nodes and Classes:")); + + _fill_classes_from(classes, "Node", class_selected); + _fill_classes_from(classes, "Resource", class_selected); + + force_detect_classes->set_text(edited->get_force_detect_classes()); + + updating_build_options = false; + + _class_list_item_selected(); +} + +void EditorBuildProfileManager::_force_detect_classes_changed(const String &p_text) { + if (updating_build_options) { + return; + } + edited->set_force_detect_classes(force_detect_classes->get_text()); +} + +void EditorBuildProfileManager::_import_profile(const String &p_path) { + Ref<EditorBuildProfile> profile; + profile.instantiate(); + Error err = profile->load_from_file(p_path); + String basefile = p_path.get_file(); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile)); + return; + } + + profile_path->set_text(p_path); + EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path); + + edited = profile; + _update_edited_profile(); +} + +void EditorBuildProfileManager::_export_profile(const String &p_path) { + ERR_FAIL_COND(edited.is_null()); + Error err = edited->save_to_file(p_path); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path)); + } else { + profile_path->set_text(p_path); + EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path); + } +} + +Ref<EditorBuildProfile> EditorBuildProfileManager::get_current_profile() { + return edited; +} + +EditorBuildProfileManager *EditorBuildProfileManager::singleton = nullptr; + +void EditorBuildProfileManager::_bind_methods() { + ClassDB::bind_method("_update_selected_profile", &EditorBuildProfileManager::_update_edited_profile); +} + +EditorBuildProfileManager::EditorBuildProfileManager() { + VBoxContainer *main_vbc = memnew(VBoxContainer); + add_child(main_vbc); + + HBoxContainer *path_hbc = memnew(HBoxContainer); + profile_path = memnew(LineEdit); + path_hbc->add_child(profile_path); + profile_path->set_editable(true); + profile_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + profile_actions[ACTION_NEW] = memnew(Button(TTR("New"))); + path_hbc->add_child(profile_actions[ACTION_NEW]); + profile_actions[ACTION_NEW]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_NEW)); + + profile_actions[ACTION_LOAD] = memnew(Button(TTR("Load"))); + path_hbc->add_child(profile_actions[ACTION_LOAD]); + profile_actions[ACTION_LOAD]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_LOAD)); + + profile_actions[ACTION_SAVE] = memnew(Button(TTR("Save"))); + path_hbc->add_child(profile_actions[ACTION_SAVE]); + profile_actions[ACTION_SAVE]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_SAVE)); + + profile_actions[ACTION_SAVE_AS] = memnew(Button(TTR("Save As"))); + path_hbc->add_child(profile_actions[ACTION_SAVE_AS]); + profile_actions[ACTION_SAVE_AS]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_SAVE_AS)); + + main_vbc->add_margin_child(TTR("Profile:"), path_hbc); + + main_vbc->add_child(memnew(HSeparator)); + + HBoxContainer *profiles_hbc = memnew(HBoxContainer); + + profile_actions[ACTION_RESET] = memnew(Button(TTR("Reset to Defaults"))); + profiles_hbc->add_child(profile_actions[ACTION_RESET]); + profile_actions[ACTION_RESET]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_RESET)); + + profile_actions[ACTION_DETECT] = memnew(Button(TTR("Detect from Project"))); + profiles_hbc->add_child(profile_actions[ACTION_DETECT]); + profile_actions[ACTION_DETECT]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_DETECT)); + + main_vbc->add_margin_child(TTR("Actions:"), profiles_hbc); + + class_list = memnew(Tree); + class_list->set_hide_root(true); + class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); + class_list->connect("cell_selected", callable_mp(this, &EditorBuildProfileManager::_class_list_item_selected)); + class_list->connect("item_edited", callable_mp(this, &EditorBuildProfileManager::_class_list_item_edited), varray(), CONNECT_DEFERRED); + class_list->connect("item_collapsed", callable_mp(this, &EditorBuildProfileManager::_class_list_item_collapsed)); + // It will be displayed once the user creates or chooses a profile. + main_vbc->add_margin_child(TTR("Configure Engine Build Profile:"), class_list, true); + + description_bit = memnew(EditorHelpBit); + description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); + main_vbc->add_margin_child(TTR("Description:"), description_bit, false); + + confirm_dialog = memnew(ConfirmationDialog); + add_child(confirm_dialog); + confirm_dialog->set_title(TTR("Please Confirm:")); + confirm_dialog->connect("confirmed", callable_mp(this, &EditorBuildProfileManager::_action_confirm)); + + import_profile = memnew(EditorFileDialog); + add_child(import_profile); + import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + import_profile->add_filter("*.build", TTR("Egine Build Profile")); + import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile)); + import_profile->set_title(TTR("Load Profile")); + import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + + export_profile = memnew(EditorFileDialog); + add_child(export_profile); + export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + export_profile->add_filter("*.build", TTR("Egine Build Profile")); + export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile)); + export_profile->set_title(TTR("Export Profile")); + export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + + force_detect_classes = memnew(LineEdit); + main_vbc->add_margin_child(TTR("Forced classes on detect:"), force_detect_classes); + force_detect_classes->connect("text_changed", callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed)); + + set_title(TTR("Edit Build Configuration Profile")); + + singleton = this; +} diff --git a/editor/editor_build_profile.h b/editor/editor_build_profile.h new file mode 100644 index 0000000000..bb6494b8c9 --- /dev/null +++ b/editor/editor_build_profile.h @@ -0,0 +1,173 @@ +/*************************************************************************/ +/* editor_build_profile.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 EDITOR_BUILD_PROFILE_H +#define EDITOR_BUILD_PROFILE_H + +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" +#include "editor/editor_help.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/option_button.h" +#include "scene/gui/separator.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tree.h" + +class EditorBuildProfile : public RefCounted { + GDCLASS(EditorBuildProfile, RefCounted); + +public: + enum BuildOption { + BUILD_OPTION_3D, + BUILD_OPTION_PHYSICS_2D, + BUILD_OPTION_PHYSICS_3D, + BUILD_OPTION_NAVIGATION, + BUILD_OPTION_XR, + BUILD_OPTION_RENDERING_DEVICE, + BUILD_OPTION_OPENGL, + BUILD_OPTION_VULKAN, + BUILD_OPTION_MAX + }; + +private: + HashSet<StringName> disabled_classes; + + HashSet<StringName> collapsed_classes; + + String force_detect_classes; + + bool build_options_disabled[BUILD_OPTION_MAX] = {}; + static const char *build_option_identifiers[BUILD_OPTION_MAX]; + static const bool build_option_disable_values[BUILD_OPTION_MAX]; + + String _get_build_option_name(BuildOption p_build_option) { return get_build_option_name(p_build_option); } + +protected: + static void _bind_methods(); + +public: + void set_disable_class(const StringName &p_class, bool p_disabled); + bool is_class_disabled(const StringName &p_class) const; + + void set_item_collapsed(const StringName &p_class, bool p_collapsed); + bool is_item_collapsed(const StringName &p_class) const; + + void set_disable_build_option(BuildOption p_build_option, bool p_disable); + bool is_build_option_disabled(BuildOption p_build_option) const; + + void set_force_detect_classes(const String &p_classes); + String get_force_detect_classes() const; + + void clear_disabled_classes(); + + Error save_to_file(const String &p_path); + Error load_from_file(const String &p_path); + + static String get_build_option_name(BuildOption p_build_option); + static String get_build_option_description(BuildOption p_build_option); + static bool get_build_option_disable_value(BuildOption p_build_option); + + EditorBuildProfile(); +}; + +VARIANT_ENUM_CAST(EditorBuildProfile::BuildOption) + +class EditorFileSystemDirectory; + +class EditorBuildProfileManager : public AcceptDialog { + GDCLASS(EditorBuildProfileManager, AcceptDialog); + + enum Action { + ACTION_NEW, + ACTION_RESET, + ACTION_LOAD, + ACTION_SAVE, + ACTION_SAVE_AS, + ACTION_DETECT, + ACTION_MAX + }; + + Action last_action = ACTION_NEW; + + ConfirmationDialog *confirm_dialog = nullptr; + Button *profile_actions[ACTION_MAX]; + + Tree *class_list = nullptr; + EditorHelpBit *description_bit = nullptr; + + EditorFileDialog *import_profile = nullptr; + EditorFileDialog *export_profile = nullptr; + + LineEdit *profile_path = nullptr; + + LineEdit *force_detect_classes = nullptr; + + void _profile_action(int p_action); + void _action_confirm(); + + void _update_edited_profile(); + void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected); + + Ref<EditorBuildProfile> edited; + + void _import_profile(const String &p_path); + void _export_profile(const String &p_path); + + bool updating_build_options = false; + + void _class_list_item_selected(); + void _class_list_item_edited(); + void _class_list_item_collapsed(Object *p_item); + void _detect_classes(); + + void _force_detect_classes_changed(const String &p_text); + + struct DetectedFile { + uint32_t timestamp = 0; + String md5; + Vector<String> classes; + }; + + void _find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected); + + static EditorBuildProfileManager *singleton; + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Ref<EditorBuildProfile> get_current_profile(); + + static EditorBuildProfileManager *get_singleton() { return singleton; } + EditorBuildProfileManager(); +}; + +#endif // EDITOR_BUILD_PROFILE_H diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index e32d4f7e9c..46907bdf8a 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -491,7 +491,7 @@ Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const { String EditorExportPlatform::find_export_template(String template_file_name, String *err) const { String current_version = VERSION_FULL_CONFIG; - String template_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version).plus_file(template_file_name); + String template_path = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(current_version).plus_file(template_file_name); if (FileAccess::exists(template_path)) { return template_path; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 166dcf19c8..ced63815b9 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -75,6 +75,7 @@ #include "editor/dependency_editor.h" #include "editor/editor_about.h" #include "editor/editor_audio_buses.h" +#include "editor/editor_build_profile.h" #include "editor/editor_command_palette.h" #include "editor/editor_data.h" #include "editor/editor_export.h" @@ -104,6 +105,7 @@ #include "editor/editor_translation_parser.h" #include "editor/export_template_manager.h" #include "editor/filesystem_dock.h" +#include "editor/import/audio_stream_import_settings.h" #include "editor/import/dynamic_font_import_settings.h" #include "editor/import/editor_import_collada.h" #include "editor/import/resource_importer_bitmask.h" @@ -131,7 +133,6 @@ #include "editor/plugins/animation_state_machine_editor.h" #include "editor/plugins/animation_tree_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" -#include "editor/plugins/audio_stream_editor_plugin.h" #include "editor/plugins/audio_stream_randomizer_editor_plugin.h" #include "editor/plugins/bit_map_editor_plugin.h" #include "editor/plugins/bone_map_editor_plugin.h" @@ -2805,6 +2806,9 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } } break; + case TOOLS_BUILD_PROFILE_MANAGER: { + build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8); + } break; case RUN_USER_DATA_FOLDER: { // Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved. OS::get_singleton()->ensure_user_data_dir(); @@ -3588,6 +3592,13 @@ void EditorNode::set_current_scene(int p_idx) { call_deferred(SNAME("_set_main_scene_state"), state, get_edited_scene()); // Do after everything else is done setting up. } +void EditorNode::setup_color_picker(ColorPicker *picker) { + int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode"); + int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); + picker->set_color_mode((ColorPicker::ColorModeType)default_color_mode); + picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); +} + bool EditorNode::is_scene_open(const String &p_path) { for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { if (editor_data.get_scene_path(i) == p_path) { @@ -5899,6 +5910,8 @@ EditorNode::EditorNode() { RenderingServer::get_singleton()->set_debug_generate_wireframes(true); + AudioServer::get_singleton()->set_enable_tagging_used_audio_streams(true); + // No navigation server by default if in editor. NavigationServer3D::get_singleton()->set_active(false); @@ -6443,6 +6456,9 @@ EditorNode::EditorNode() { scene_import_settings = memnew(SceneImportSettings); gui_base->add_child(scene_import_settings); + audio_stream_import_settings = memnew(AudioStreamImportSettings); + gui_base->add_child(audio_stream_import_settings); + fontdata_import_settings = memnew(DynamicFontImportSettings); gui_base->add_child(fontdata_import_settings); @@ -6451,6 +6467,10 @@ EditorNode::EditorNode() { feature_profile_manager = memnew(EditorFeatureProfileManager); gui_base->add_child(feature_profile_manager); + + build_profile_manager = memnew(EditorBuildProfileManager); + gui_base->add_child(build_profile_manager); + about = memnew(EditorAbout); gui_base->add_child(about); feature_profile_manager->connect("current_feature_profile_changed", callable_mp(this, &EditorNode::_feature_profile_changed)); @@ -6543,6 +6563,10 @@ EditorNode::EditorNode() { p->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE); p->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER); + p->add_separator(); + p->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER); + p->add_separator(); + plugin_config_dialog = memnew(PluginConfigDialog); plugin_config_dialog->connect("plugin_ready", callable_mp(this, &EditorNode::_on_plugin_ready)); gui_base->add_child(plugin_config_dialog); @@ -7079,7 +7103,6 @@ EditorNode::EditorNode() { // This list is alphabetized, and plugins that depend on Node2D are in their own section below. add_editor_plugin(memnew(AnimationTreeEditorPlugin)); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); - add_editor_plugin(memnew(AudioStreamEditorPlugin)); add_editor_plugin(memnew(AudioStreamRandomizerEditorPlugin)); add_editor_plugin(memnew(BitMapEditorPlugin)); add_editor_plugin(memnew(BoneMapEditorPlugin)); @@ -7190,6 +7213,7 @@ EditorNode::EditorNode() { vshader_convert.instantiate(); resource_conversion_plugins.push_back(vshader_convert); } + update_spinner_step_msec = OS::get_singleton()->get_ticks_msec(); update_spinner_step_frame = Engine::get_singleton()->get_frames_drawn(); diff --git a/editor/editor_node.h b/editor/editor_node.h index 07d565314d..53c2312022 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -88,6 +88,7 @@ class ProjectExportDialog; class ProjectSettingsEditor; class RunSettingsDialog; class SceneImportSettings; +class AudioStreamImportSettings; class ScriptCreateDialog; class SubViewport; class TabBar; @@ -95,6 +96,7 @@ class TabContainer; class TextureProgressBar; class VSplitContainer; class Window; +class EditorBuildProfileManager; class EditorNode : public Node { GDCLASS(EditorNode, Node); @@ -163,6 +165,7 @@ private: EDIT_REDO, EDIT_RELOAD_SAVED_SCENE, TOOLS_ORPHAN_RESOURCES, + TOOLS_BUILD_PROFILE_MANAGER, TOOLS_CUSTOM, RESOURCE_SAVE, RESOURCE_SAVE_AS, @@ -377,6 +380,7 @@ private: EditorFileDialog *file = nullptr; ExportTemplateManager *export_template_manager = nullptr; EditorFeatureProfileManager *feature_profile_manager = nullptr; + EditorBuildProfileManager *build_profile_manager = nullptr; EditorFileDialog *file_templates = nullptr; EditorFileDialog *file_export_lib = nullptr; EditorFileDialog *file_script = nullptr; @@ -468,6 +472,7 @@ private: DynamicFontImportSettings *fontdata_import_settings = nullptr; SceneImportSettings *scene_import_settings = nullptr; + AudioStreamImportSettings *audio_stream_import_settings = nullptr; String import_reload_fn; @@ -783,6 +788,8 @@ public: void set_current_version(uint64_t p_version); void set_current_scene(int p_idx); + void setup_color_picker(ColorPicker *picker); + void request_instance_scene(const String &p_path); void request_instantiate_scenes(const Vector<String> &p_files); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index aaa518362c..e9354927c8 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3007,14 +3007,6 @@ void EditorPropertyColor::_popup_closed() { } } -void EditorPropertyColor::_picker_created() { - // get default color picker mode from editor settings - int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode"); - picker->get_picker()->set_color_mode((ColorPicker::ColorModeType)default_color_mode); - int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); - picker->get_picker()->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); -} - void EditorPropertyColor::_picker_opening() { last_color = picker->get_pick_color(); } @@ -3059,7 +3051,7 @@ EditorPropertyColor::EditorPropertyColor() { picker->set_flat(true); picker->connect("color_changed", callable_mp(this, &EditorPropertyColor::_color_changed)); picker->connect("popup_closed", callable_mp(this, &EditorPropertyColor::_popup_closed)); - picker->connect("picker_created", callable_mp(this, &EditorPropertyColor::_picker_created)); + picker->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(picker->get_picker())); picker->get_popup()->connect("about_to_popup", callable_mp(this, &EditorPropertyColor::_picker_opening)); } @@ -3177,6 +3169,11 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const Node *dropped_node = get_tree()->get_edited_scene_root()->get_node(nodes[0]); ERR_FAIL_NULL_V(dropped_node, false); + if (valid_types.is_empty()) { + // No type requirements specified so any type is valid. + return true; + } + for (const StringName &E : valid_types) { if (dropped_node->is_class(E)) { return true; @@ -3517,6 +3514,9 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const shader_picker->set_edited_material(Object::cast_to<ShaderMaterial>(p_object)); resource_picker = shader_picker; connect(SNAME("ready"), callable_mp(this, &EditorPropertyResource::_update_preferred_shader)); + } else if (p_base_type == "AudioStream") { + EditorAudioStreamPicker *astream_picker = memnew(EditorAudioStreamPicker); + resource_picker = astream_picker; } else { resource_picker = memnew(EditorResourcePicker); } diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 40e16bf717..da5ab08d49 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -30,6 +30,7 @@ #include "editor_resource_picker.h" +#include "editor/audio_stream_preview.h" #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" @@ -47,32 +48,37 @@ void EditorResourcePicker::clear_caches() { } void EditorResourcePicker::_update_resource() { - preview_rect->set_texture(Ref<Texture2D>()); - assign_button->set_custom_minimum_size(Size2(1, 1)); + String resource_path; + if (edited_resource.is_valid() && edited_resource->get_path().is_resource_file()) { + resource_path = edited_resource->get_path() + "\n"; + } - if (edited_resource == Ref<Resource>()) { - assign_button->set_icon(Ref<Texture2D>()); - assign_button->set_text(TTR("[empty]")); - assign_button->set_tooltip(""); - } else { - assign_button->set_icon(EditorNode::get_singleton()->get_object_icon(edited_resource.operator->(), "Object")); + if (preview_rect) { + preview_rect->set_texture(Ref<Texture2D>()); + + assign_button->set_custom_minimum_size(assign_button_min_size); - if (!edited_resource->get_name().is_empty()) { - assign_button->set_text(edited_resource->get_name()); - } else if (edited_resource->get_path().is_resource_file()) { - assign_button->set_text(edited_resource->get_path().get_file()); + if (edited_resource == Ref<Resource>()) { + assign_button->set_icon(Ref<Texture2D>()); + assign_button->set_text(TTR("[empty]")); + assign_button->set_tooltip(""); } else { - assign_button->set_text(edited_resource->get_class()); - } + assign_button->set_icon(EditorNode::get_singleton()->get_object_icon(edited_resource.operator->(), "Object")); - String resource_path; - if (edited_resource->get_path().is_resource_file()) { - resource_path = edited_resource->get_path() + "\n"; + if (!edited_resource->get_name().is_empty()) { + assign_button->set_text(edited_resource->get_name()); + } else if (edited_resource->get_path().is_resource_file()) { + assign_button->set_text(edited_resource->get_path().get_file()); + } else { + assign_button->set_text(edited_resource->get_class()); + } + assign_button->set_tooltip(resource_path + TTR("Type:") + " " + edited_resource->get_class()); + + // Preview will override the above, so called at the end. + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(edited_resource, this, "_update_resource_preview", edited_resource->get_instance_id()); } + } else if (edited_resource.is_valid()) { assign_button->set_tooltip(resource_path + TTR("Type:") + " " + edited_resource->get_class()); - - // Preview will override the above, so called at the end. - EditorResourcePreview::get_singleton()->queue_edited_resource_preview(edited_resource, this, "_update_resource_preview", edited_resource->get_instance_id()); } } @@ -81,28 +87,30 @@ void EditorResourcePicker::_update_resource_preview(const String &p_path, const return; } - Ref<Script> script = edited_resource; - if (script.is_valid()) { - assign_button->set_text(script->get_path().get_file()); - return; - } + if (preview_rect) { + Ref<Script> script = edited_resource; + if (script.is_valid()) { + assign_button->set_text(script->get_path().get_file()); + return; + } - if (p_preview.is_valid()) { - preview_rect->set_offset(SIDE_LEFT, assign_button->get_icon()->get_width() + assign_button->get_theme_stylebox(SNAME("normal"))->get_default_margin(SIDE_LEFT) + get_theme_constant(SNAME("h_separation"), SNAME("Button"))); + if (p_preview.is_valid()) { + preview_rect->set_offset(SIDE_LEFT, assign_button->get_icon()->get_width() + assign_button->get_theme_stylebox(SNAME("normal"))->get_default_margin(SIDE_LEFT) + get_theme_constant(SNAME("h_separation"), SNAME("Button"))); - // Resource-specific stretching. - if (Ref<GradientTexture1D>(edited_resource).is_valid() || Ref<Gradient>(edited_resource).is_valid()) { - preview_rect->set_stretch_mode(TextureRect::STRETCH_SCALE); - assign_button->set_custom_minimum_size(Size2(1, 1)); - } else { - preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); - int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); - thumbnail_size *= EDSCALE; - assign_button->set_custom_minimum_size(Size2(1, thumbnail_size)); - } + // Resource-specific stretching. + if (Ref<GradientTexture1D>(edited_resource).is_valid() || Ref<Gradient>(edited_resource).is_valid()) { + preview_rect->set_stretch_mode(TextureRect::STRETCH_SCALE); + assign_button->set_custom_minimum_size(assign_button_min_size); + } else { + preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); + thumbnail_size *= EDSCALE; + assign_button->set_custom_minimum_size(Size2(MIN(1, assign_button_min_size.x), MIN(thumbnail_size, assign_button_min_size.y))); + } - preview_rect->set_texture(p_preview); - assign_button->set_text(""); + preview_rect->set_texture(p_preview); + assign_button->set_text(""); + } } } @@ -866,7 +874,7 @@ void EditorResourcePicker::_ensure_resource_menu() { edit_menu->connect("popup_hide", callable_mp((BaseButton *)edit_button, &BaseButton::set_pressed), varray(false)); } -EditorResourcePicker::EditorResourcePicker() { +EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) { assign_button = memnew(Button); assign_button->set_flat(true); assign_button->set_h_size_flags(SIZE_EXPAND_FILL); @@ -877,13 +885,15 @@ EditorResourcePicker::EditorResourcePicker() { assign_button->connect("draw", callable_mp(this, &EditorResourcePicker::_button_draw)); assign_button->connect("gui_input", callable_mp(this, &EditorResourcePicker::_button_input)); - preview_rect = memnew(TextureRect); - preview_rect->set_ignore_texture_size(true); - preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT); - preview_rect->set_offset(SIDE_TOP, 1); - preview_rect->set_offset(SIDE_BOTTOM, -1); - preview_rect->set_offset(SIDE_RIGHT, -1); - assign_button->add_child(preview_rect); + if (!p_hide_assign_button_controls) { + preview_rect = memnew(TextureRect); + preview_rect->set_ignore_texture_size(true); + preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + preview_rect->set_offset(SIDE_TOP, 1); + preview_rect->set_offset(SIDE_BOTTOM, -1); + preview_rect->set_offset(SIDE_RIGHT, -1); + assign_button->add_child(preview_rect); + } edit_button = memnew(Button); edit_button->set_flat(true); @@ -993,3 +1003,176 @@ void EditorShaderPicker::set_preferred_mode(int p_mode) { EditorShaderPicker::EditorShaderPicker() { } + +////////////// + +void EditorAudioStreamPicker::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: + case NOTIFICATION_THEME_CHANGED: { + _update_resource(); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + Ref<AudioStream> audio_stream = get_edited_resource(); + if (audio_stream.is_valid()) { + if (audio_stream->get_length() > 0) { + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream); + if (preview.is_valid()) { + if (preview->get_version() != last_preview_version) { + stream_preview_rect->update(); + last_preview_version = preview->get_version(); + } + } + } + + uint64_t tagged_frame = audio_stream->get_tagged_frame(); + uint64_t diff_frames = AudioServer::get_singleton()->get_mixed_frames() - tagged_frame; + uint64_t diff_msec = diff_frames * 1000 / AudioServer::get_singleton()->get_mix_rate(); + + if (diff_msec < 300) { + uint32_t count = audio_stream->get_tagged_frame_count(); + + bool differ = false; + + if (count != tagged_frame_offset_count) { + differ = true; + } + float offsets[MAX_TAGGED_FRAMES]; + + for (uint32_t i = 0; i < MIN(count, uint32_t(MAX_TAGGED_FRAMES)); i++) { + offsets[i] = audio_stream->get_tagged_frame_offset(i); + if (offsets[i] != tagged_frame_offsets[i]) { + differ = true; + } + } + + if (differ) { + tagged_frame_offset_count = count; + for (uint32_t i = 0; i < count; i++) { + tagged_frame_offsets[i] = offsets[i]; + } + } + + stream_preview_rect->update(); + } else { + if (tagged_frame_offset_count != 0) { + stream_preview_rect->update(); + } + tagged_frame_offset_count = 0; + } + } + } break; + } +} + +void EditorAudioStreamPicker::_update_resource() { + EditorResourcePicker::_update_resource(); + + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Ref<AudioStream> audio_stream = get_edited_resource(); + if (audio_stream.is_valid() && audio_stream->get_length() > 0.0) { + set_assign_button_min_size(Size2(1, font->get_height(font_size) * 3)); + } else { + set_assign_button_min_size(Size2(1, font->get_height(font_size) * 1.5)); + } + + stream_preview_rect->update(); +} + +void EditorAudioStreamPicker::_preview_draw() { + Ref<AudioStream> audio_stream = get_edited_resource(); + if (!audio_stream.is_valid()) { + get_assign_button()->set_text(TTR("[empty]")); + return; + } + + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + + get_assign_button()->set_text(""); + + Size2i size = stream_preview_rect->get_size(); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + + Rect2 rect(Point2(), size); + + if (audio_stream->get_length() > 0) { + rect.size.height *= 0.5; + + stream_preview_rect->draw_rect(rect, Color(0, 0, 0, 1)); + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream); + float preview_len = preview->get_length(); + + Vector<Vector2> lines; + lines.resize(size.width * 2); + + for (int i = 0; i < size.width; i++) { + float ofs = i * preview_len / size.width; + float ofs_n = (i + 1) * preview_len / size.width; + float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; + float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; + + int idx = i; + lines.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y); + lines.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y); + } + + Vector<Color> color; + color.push_back(get_theme_color(SNAME("contrast_color_2"), SNAME("Editor"))); + + RS::get_singleton()->canvas_item_add_multiline(stream_preview_rect->get_canvas_item(), lines, color); + + if (tagged_frame_offset_count) { + Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + + for (uint32_t i = 0; i < tagged_frame_offset_count; i++) { + int x = CLAMP(tagged_frame_offsets[i] * size.width / preview_len, 0, size.width); + if (x == 0) { + continue; // Because some may always return 0, ignore offset 0. + } + stream_preview_rect->draw_rect(Rect2i(x, 0, 2, rect.size.height), accent); + } + } + rect.position.y += rect.size.height; + } + + Ref<Texture2D> icon; + Color icon_modulate(1, 1, 1, 1); + + if (tagged_frame_offset_count > 0) { + icon = get_theme_icon(SNAME("Play"), SNAME("EditorIcons")); + if ((OS::get_singleton()->get_ticks_msec() % 500) > 250) { + icon_modulate = Color(1, 0.5, 0.5, 1); // get_theme_color(SNAME("accent_color"), SNAME("Editor")); + } + } else { + icon = EditorNode::get_singleton()->get_object_icon(audio_stream.operator->(), "Object"); + } + String text; + if (!audio_stream->get_name().is_empty()) { + text = audio_stream->get_name(); + } else if (audio_stream->get_path().is_resource_file()) { + text = audio_stream->get_path().get_file(); + } else { + text = audio_stream->get_class().replace_first("AudioStream", ""); + } + + stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 2, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate); + stream_preview_rect->draw_string(font, Point2i(EDSCALE * 2 + icon->get_width(), rect.position.y + font->get_ascent(font_size) + (rect.size.height - font->get_height(font_size)) / 2), text, HORIZONTAL_ALIGNMENT_CENTER, size.width - 4 * EDSCALE - icon->get_width()); +} + +EditorAudioStreamPicker::EditorAudioStreamPicker() : + EditorResourcePicker(true) { + stream_preview_rect = memnew(Control); + + stream_preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + stream_preview_rect->set_offset(SIDE_TOP, 1); + stream_preview_rect->set_offset(SIDE_BOTTOM, -1); + stream_preview_rect->set_offset(SIDE_RIGHT, -1); + stream_preview_rect->set_mouse_filter(MOUSE_FILTER_IGNORE); + stream_preview_rect->connect("draw", callable_mp(this, &EditorAudioStreamPicker::_preview_draw)); + + get_assign_button()->add_child(stream_preview_rect); + get_assign_button()->move_child(stream_preview_rect, 0); + set_process_internal(true); +} diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index 8e26e1f4c0..d36e742bcd 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -58,6 +58,8 @@ class EditorResourcePicker : public HBoxContainer { EditorFileDialog *file_dialog = nullptr; EditorQuickOpen *quick_open = nullptr; + Size2i assign_button_min_size = Size2i(1, 1); + enum MenuOption { OBJ_MENU_LOAD, OBJ_MENU_QUICKLOAD, @@ -75,7 +77,6 @@ class EditorResourcePicker : public HBoxContainer { PopupMenu *edit_menu = nullptr; - void _update_resource(); void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj); void _resource_selected(); @@ -100,9 +101,17 @@ class EditorResourcePicker : public HBoxContainer { void _ensure_resource_menu(); protected: + virtual void _update_resource(); + + Button *get_assign_button() { return assign_button; } static void _bind_methods(); void _notification(int p_what); + void set_assign_button_min_size(const Size2i &p_size) { + assign_button_min_size = p_size; + assign_button->set_custom_minimum_size(assign_button_min_size); + } + GDVIRTUAL1(_set_create_options, Object *) GDVIRTUAL1R(bool, _handle_menu_selected, int) @@ -126,7 +135,7 @@ public: virtual void set_create_options(Object *p_menu_node); virtual bool handle_menu_selected(int p_which); - EditorResourcePicker(); + EditorResourcePicker(bool p_hide_assign_button_controls = false); }; class EditorScriptPicker : public EditorResourcePicker { @@ -173,4 +182,26 @@ public: EditorShaderPicker(); }; +class EditorAudioStreamPicker : public EditorResourcePicker { + GDCLASS(EditorAudioStreamPicker, EditorResourcePicker); + + uint64_t last_preview_version = 0; + Control *stream_preview_rect = nullptr; + + enum { + MAX_TAGGED_FRAMES = 8 + }; + float tagged_frame_offsets[MAX_TAGGED_FRAMES]; + uint32_t tagged_frame_offset_count = 0; + + void _preview_draw(); + virtual void _update_resource() override; + +protected: + void _notification(int p_what); + +public: + EditorAudioStreamPicker(); +}; + #endif // EDITOR_RESOURCE_PICKER_H diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index ba49c6dc5f..6ce8625daa 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -55,7 +55,7 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { args.push_back("--remote-debug"); args.push_back(EditorDebuggerNode::get_singleton()->get_server_uri()); - args.push_back("--allow_focus_steal_pid"); + args.push_back("--editor-pid"); args.push_back(itos(OS::get_singleton()->get_process_id())); bool debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisons", false); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index abb1b73a18..40ca058406 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -1105,8 +1105,8 @@ void EditorSettings::add_property_hint(const PropertyInfo &p_hint) { // Editor data and config directories // EditorPaths::create() is responsible for the creation of these directories. -String EditorSettings::get_templates_dir() const { - return EditorPaths::get_singleton()->get_data_dir().plus_file("templates"); +String EditorSettings::get_export_templates_dir() const { + return EditorPaths::get_singleton()->get_data_dir().plus_file("export_templates"); } String EditorSettings::get_project_settings_dir() const { diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 43f90f9258..56c73685bb 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -151,7 +151,7 @@ public: Ref<Resource> get_resource_clipboard() const { return clipboard; } String get_data_dir() const; - String get_templates_dir() const; + String get_export_templates_dir() const; String get_project_settings_dir() const; String get_text_editor_themes_dir() const; String get_script_templates_dir() const; diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index af9c918360..9f9b8374ce 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -46,7 +46,7 @@ void ExportTemplateManager::_update_template_status() { // Fetch installed templates from the file system. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - const String &templates_dir = EditorSettings::get_singleton()->get_templates_dir(); + const String &templates_dir = EditorSettings::get_singleton()->get_export_templates_dir(); Error err = da->change_dir(templates_dir); ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'."); @@ -439,7 +439,7 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_ } Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - String template_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(version); + String template_path = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(version); Error err = d->make_dir_recursive(template_path); if (err != OK) { EditorNode::get_singleton()->show_warning(TTR("Error creating path for extracting templates:") + "\n" + template_path); @@ -538,7 +538,7 @@ void ExportTemplateManager::_uninstall_template(const String &p_version) { void ExportTemplateManager::_uninstall_template_confirmed() { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - const String &templates_dir = EditorSettings::get_singleton()->get_templates_dir(); + const String &templates_dir = EditorSettings::get_singleton()->get_export_templates_dir(); Error err = da->change_dir(templates_dir); ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'."); @@ -616,7 +616,7 @@ void ExportTemplateManager::_installed_table_button_cbk(Object *p_item, int p_co } void ExportTemplateManager::_open_template_folder(const String &p_version) { - const String &templates_dir = EditorSettings::get_singleton()->get_templates_dir(); + const String &templates_dir = EditorSettings::get_singleton()->get_export_templates_dir(); OS::get_singleton()->shell_open("file://" + templates_dir.plus_file(p_version)); } @@ -640,12 +640,12 @@ void ExportTemplateManager::_hide_dialog() { } bool ExportTemplateManager::can_install_android_template() { - const String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + const String templates_dir = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG); return FileAccess::exists(templates_dir.plus_file("android_source.zip")); } Error ExportTemplateManager::install_android_template() { - const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + const String &templates_path = EditorSettings::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG); const String &source_zip = templates_path.plus_file("android_source.zip"); ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN); return install_android_template_from_file(source_zip); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index fe6e6044a4..e025cedf02 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -985,7 +985,9 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit } } - if (ResourceLoader::get_resource_type(fpath) == "PackedScene") { + String resource_type = ResourceLoader::get_resource_type(fpath); + + if (resource_type == "PackedScene") { bool is_imported = false; { @@ -1005,7 +1007,7 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit } else { EditorNode::get_singleton()->open_request(fpath); } - } else if (ResourceLoader::get_resource_type(fpath) == "AnimationLibrary") { + } else if (resource_type == "AnimationLibrary") { bool is_imported = false; { @@ -1025,6 +1027,25 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit } else { EditorNode::get_singleton()->open_request(fpath); } + } else if (ResourceLoader::is_imported(fpath)) { + // If the importer has advanced settings, show them. + int order; + bool can_threads; + String name; + Error err = ResourceFormatImporter::get_singleton()->get_import_order_threads_and_importer(fpath, order, can_threads, name); + bool used_advanced_settings = false; + if (err == OK) { + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(name); + if (importer.is_valid() && importer->has_advanced_options()) { + importer->show_advanced_options(fpath); + used_advanced_settings = true; + } + } + + if (!used_advanced_settings) { + EditorNode::get_singleton()->load_resource(fpath); + } + } else { EditorNode::get_singleton()->load_resource(fpath); } @@ -2034,6 +2055,10 @@ void FileSystemDock::_resource_created() { make_shader_dialog->config(fpath.plus_file("new_shader"), false, false, 1); make_shader_dialog->popup_centered(); return; + } else if (type_name == "ShaderInclude") { + make_shader_dialog->config(fpath.plus_file("new_shader_include"), false, false, 2); + make_shader_dialog->popup_centered(); + return; } Variant c = new_resource_dialog->instance_selected(); diff --git a/editor/import/audio_stream_import_settings.cpp b/editor/import/audio_stream_import_settings.cpp new file mode 100644 index 0000000000..f3709efab6 --- /dev/null +++ b/editor/import/audio_stream_import_settings.cpp @@ -0,0 +1,650 @@ +/*************************************************************************/ +/* audio_stream_import_settings.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "audio_stream_import_settings.h" +#include "editor/audio_stream_preview.h" +#include "editor/editor_file_system.h" +#include "editor/editor_scale.h" + +AudioStreamImportSettings *AudioStreamImportSettings::singleton = nullptr; + +void AudioStreamImportSettings::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AudioStreamImportSettings::_preview_changed)); + connect("confirmed", callable_mp(this, &AudioStreamImportSettings::_reimport)); + } break; + + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + _stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + _preview->set_color(get_theme_color(SNAME("dark_color_2"), SNAME("Editor"))); + color_rect->set_color(get_theme_color(SNAME("dark_color_1"), SNAME("Editor"))); + _current_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + _current_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); + _duration_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + _duration_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); + + zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); + zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); + zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); + + _indicator->update(); + _preview->update(); + } break; + + case NOTIFICATION_PROCESS: { + _current = _player->get_playback_position(); + _indicator->update(); + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + _stop(); + } + } break; + } +} + +void AudioStreamImportSettings::_draw_preview() { + Rect2 rect = _preview->get_rect(); + Size2 size = rect.size; + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + float preview_offset = zoom_bar->get_value(); + float preview_len = zoom_bar->get_page(); + + Ref<Font> beat_font = get_theme_font(SNAME("main"), SNAME("EditorFonts")); + int main_size = get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")); + Vector<Vector2> lines; + lines.resize(size.width * 2); + Color color_active = get_theme_color(SNAME("contrast_color_2"), SNAME("Editor")); + Color color_inactive = color_active; + color_inactive.a *= 0.5; + Vector<Color> color; + color.resize(lines.size()); + + float inactive_from = 1e20; + float beat_size = 0; + int last_beat = 0; + if (stream->get_bpm() > 0) { + beat_size = 60 / float(stream->get_bpm()); + int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE; + rect.position.y += y_ofs; + rect.size.y -= y_ofs; + + if (stream->get_beat_count() > 0) { + last_beat = stream->get_beat_count(); + inactive_from = last_beat * beat_size; + } + } + + for (int i = 0; i < size.width; i++) { + float ofs = preview_offset + i * preview_len / size.width; + float ofs_n = preview_offset + (i + 1) * preview_len / size.width; + float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; + float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; + + int idx = i; + lines.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y); + lines.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y); + + Color c = ofs > inactive_from ? color_inactive : color_active; + + color.write[idx * 2 + 0] = c; + color.write[idx * 2 + 1] = c; + } + + RS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), lines, color); + + if (beat_size) { + Color beat_color = Color(1, 1, 1, 1); + Color final_beat_color = beat_color; + Color bar_color = beat_color; + beat_color.a *= 0.4; + bar_color.a *= 0.6; + + int prev_beat = 0; // Do not draw beat zero + Color color_bg = color_active; + color_bg.a *= 0.2; + _preview->draw_rect(Rect2(0, 0, rect.size.width, rect.position.y), color_bg); + int bar_beats = stream->get_bar_beats(); + + int last_text_end_x = 0; + for (int i = 0; i < size.width; i++) { + float ofs = preview_offset + i * preview_len / size.width; + int beat = int(ofs / beat_size); + if (beat != prev_beat) { + String text = itos(beat); + int text_w = beat_font->get_string_size(text).width; + if (i - text_w / 2 > last_text_end_x + 2 * EDSCALE) { + int x_ofs = i - text_w / 2; + _preview->draw_string(beat_font, Point2(x_ofs, 2 * EDSCALE + beat_font->get_ascent(main_size)), text, HORIZONTAL_ALIGNMENT_LEFT, rect.size.width - x_ofs, Font::DEFAULT_FONT_SIZE, color_active); + last_text_end_x = i + text_w / 2; + } + + if (beat == last_beat) { + _preview->draw_rect(Rect2i(i, rect.position.y, 2, rect.size.height), final_beat_color); + // Darken subsequent beats + beat_color.a *= 0.3; + color_active.a *= 0.3; + } else { + _preview->draw_rect(Rect2i(i, rect.position.y, 1, rect.size.height), (beat % bar_beats) == 0 ? bar_color : beat_color); + } + prev_beat = beat; + } + } + } +} + +void AudioStreamImportSettings::_preview_changed(ObjectID p_which) { + if (stream.is_valid() && stream->get_instance_id() == p_which) { + _preview->update(); + } +} + +void AudioStreamImportSettings::_preview_zoom_in() { + if (!stream.is_valid()) { + return; + } + float page_size = zoom_bar->get_page(); + zoom_bar->set_page(page_size * 0.5); + zoom_bar->set_value(zoom_bar->get_value() + page_size * 0.25); + + _preview->update(); + _indicator->update(); +} + +void AudioStreamImportSettings::_preview_zoom_out() { + if (!stream.is_valid()) { + return; + } + float page_size = zoom_bar->get_page(); + zoom_bar->set_page(MIN(zoom_bar->get_max(), page_size * 2.0)); + zoom_bar->set_value(zoom_bar->get_value() - page_size * 0.5); + + _preview->update(); + _indicator->update(); +} + +void AudioStreamImportSettings::_preview_zoom_reset() { + if (!stream.is_valid()) { + return; + } + zoom_bar->set_max(stream->get_length()); + zoom_bar->set_page(zoom_bar->get_max()); + zoom_bar->set_value(0); + _preview->update(); + _indicator->update(); +} + +void AudioStreamImportSettings::_preview_zoom_offset_changed(double) { + _preview->update(); + _indicator->update(); +} + +void AudioStreamImportSettings::_audio_changed() { + if (!is_visible()) { + return; + } + _preview->update(); + _indicator->update(); + color_rect->update(); +} + +void AudioStreamImportSettings::_play() { + if (_player->is_playing()) { + // '_pausing' variable indicates that we want to pause the audio player, not stop it. See '_on_finished()'. + _pausing = true; + _player->stop(); + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + set_process(false); + } else { + _player->play(_current); + _play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); + set_process(true); + } +} + +void AudioStreamImportSettings::_stop() { + _player->stop(); + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + _current = 0; + _indicator->update(); + set_process(false); +} + +void AudioStreamImportSettings::_on_finished() { + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + if (!_pausing) { + _current = 0; + _indicator->update(); + } else { + _pausing = false; + } + set_process(false); +} + +void AudioStreamImportSettings::_draw_indicator() { + if (!stream.is_valid()) { + return; + } + + Rect2 rect = _preview->get_rect(); + + Ref<Font> beat_font = get_theme_font(SNAME("main"), SNAME("EditorFonts")); + int main_size = get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")); + + if (stream->get_bpm() > 0) { + int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE; + rect.position.y += y_ofs; + rect.size.height -= y_ofs; + } + + float ofs_x = (_current - zoom_bar->get_value()) * rect.size.width / zoom_bar->get_page(); + if (ofs_x < 0 || ofs_x >= rect.size.width) { + return; + } + + const Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + _indicator->draw_line(Point2(ofs_x, rect.position.y), Point2(ofs_x, rect.position.y + rect.size.height), color, Math::round(2 * EDSCALE)); + _indicator->draw_texture( + get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons")), + Point2(ofs_x - get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons"))->get_width() * 0.5, rect.position.y), + color); + + if (stream->get_bpm() > 0 && _hovering_beat != -1) { + // Draw hovered beat. + float preview_offset = zoom_bar->get_value(); + float preview_len = zoom_bar->get_page(); + float beat_size = 60 / float(stream->get_bpm()); + int prev_beat = 0; + int last_text_end_x = 0; + for (int i = 0; i < rect.size.width; i++) { + float ofs = preview_offset + i * preview_len / rect.size.width; + int beat = int(ofs / beat_size); + if (beat != prev_beat) { + String text = itos(beat); + int text_w = beat_font->get_string_size(text).width; + if (i - text_w / 2 > last_text_end_x + 2 * EDSCALE && beat == _hovering_beat) { + int x_ofs = i - text_w / 2; + _indicator->draw_string(beat_font, Point2(x_ofs, 2 * EDSCALE + beat_font->get_ascent(main_size)), text, HORIZONTAL_ALIGNMENT_LEFT, rect.size.width - x_ofs, Font::DEFAULT_FONT_SIZE, color); + last_text_end_x = i + text_w / 2; + break; + } + prev_beat = beat; + } + } + } + + _current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /"); +} + +void AudioStreamImportSettings::_on_indicator_mouse_exited() { + _hovering_beat = -1; + _indicator->update(); +} + +void AudioStreamImportSettings::_on_input_indicator(Ref<InputEvent> p_event) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (stream->get_bpm() > 0) { + int main_size = get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")); + Ref<Font> beat_font = get_theme_font(SNAME("main"), SNAME("EditorFonts")); + int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE; + if ((!_dragging && mb->get_position().y < y_ofs) || _beat_len_dragging) { + if (mb->is_pressed()) { + _set_beat_len_to(mb->get_position().x); + _beat_len_dragging = true; + } else { + _beat_len_dragging = false; + } + return; + } + } + + if (mb->is_pressed()) { + _seek_to(mb->get_position().x); + } + _dragging = mb->is_pressed(); + } + + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (_dragging) { + _seek_to(mm->get_position().x); + } + if (_beat_len_dragging) { + _set_beat_len_to(mm->get_position().x); + } + if (stream->get_bpm() > 0) { + int main_size = get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")); + Ref<Font> beat_font = get_theme_font(SNAME("main"), SNAME("EditorFonts")); + int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE; + if (mm->get_position().y < y_ofs) { + int new_hovering_beat = _get_beat_at_pos(mm->get_position().x); + if (new_hovering_beat != _hovering_beat) { + _hovering_beat = new_hovering_beat; + _indicator->update(); + } + } else if (_hovering_beat != -1) { + _hovering_beat = -1; + _indicator->update(); + } + } + } +} + +int AudioStreamImportSettings::_get_beat_at_pos(real_t p_x) { + float ofs_sec = zoom_bar->get_value() + p_x * zoom_bar->get_page() / _preview->get_size().width; + ofs_sec = CLAMP(ofs_sec, 0, stream->get_length()); + float beat_size = 60 / float(stream->get_bpm()); + int beat = int(ofs_sec / beat_size + 0.5); + + if (beat * beat_size > stream->get_length() + 0.001) { // Stream may end few audio frames before but may still want to use full loop. + beat--; + } + return beat; +} + +void AudioStreamImportSettings::_set_beat_len_to(real_t p_x) { + int beat = _get_beat_at_pos(p_x); + if (beat < 1) { + beat = 1; // Because 0 is disable. + } + updating_settings = true; + beats_enabled->set_pressed(true); + beats_edit->set_value(beat); + updating_settings = false; + _settings_changed(); +} + +void AudioStreamImportSettings::_seek_to(real_t p_x) { + _current = zoom_bar->get_value() + p_x / _preview->get_rect().size.x * zoom_bar->get_page(); + _current = CLAMP(_current, 0, stream->get_length()); + _player->seek(_current); + _indicator->update(); +} + +void AudioStreamImportSettings::edit(const String &p_path, const String &p_importer, const Ref<AudioStream> &p_stream) { + if (!stream.is_null()) { + stream->disconnect("changed", callable_mp(this, &AudioStreamImportSettings::_audio_changed)); + } + + importer = p_importer; + path = p_path; + + stream = p_stream; + _player->set_stream(stream); + _current = 0; + String text = String::num(stream->get_length(), 2).pad_decimals(2) + "s"; + _duration_label->set_text(text); + + if (!stream.is_null()) { + stream->connect("changed", callable_mp(this, &AudioStreamImportSettings::_audio_changed)); + _preview->update(); + _indicator->update(); + color_rect->update(); + } else { + hide(); + } + params.clear(); + + if (stream.is_valid()) { + Ref<ConfigFile> config_file; + config_file.instantiate(); + Error err = config_file->load(p_path + ".import"); + updating_settings = true; + if (err == OK) { + double bpm = config_file->get_value("params", "bpm", 0); + int beats = config_file->get_value("params", "beat_count", 0); + bpm_edit->set_value(bpm > 0 ? bpm : 120); + bpm_enabled->set_pressed(bpm > 0); + beats_edit->set_value(beats); + beats_enabled->set_pressed(beats > 0); + loop->set_pressed(config_file->get_value("params", "loop", false)); + loop_offset->set_value(config_file->get_value("params", "loop_offset", 0)); + bar_beats_edit->set_value(config_file->get_value("params", "bar_beats", 4)); + + List<String> keys; + config_file->get_section_keys("params", &keys); + for (const String &K : keys) { + params[K] = config_file->get_value("params", K); + } + } else { + bpm_edit->set_value(false); + bpm_enabled->set_pressed(false); + beats_edit->set_value(0); + beats_enabled->set_pressed(false); + bar_beats_edit->set_value(4); + loop->set_pressed(false); + loop_offset->set_value(0); + } + + _preview_zoom_reset(); + updating_settings = false; + _settings_changed(); + + set_title(vformat(TTR("Audio Stream Importer: %s"), p_path.get_file())); + popup_centered(); + } +} + +void AudioStreamImportSettings::_settings_changed() { + if (updating_settings) { + return; + } + + updating_settings = true; + stream->call("set_loop", loop->is_pressed()); + stream->call("set_loop_offset", loop_offset->get_value()); + if (bpm_enabled->is_pressed()) { + stream->call("set_bpm", bpm_edit->get_value()); + beats_enabled->show(); + beats_edit->show(); + bar_beats_label->show(); + bar_beats_edit->show(); + double bpm = bpm_edit->get_value(); + if (bpm > 0) { + float beat_size = 60 / float(bpm); + int beat_max = int((stream->get_length() + 0.001) / beat_size); + int current_beat = beats_edit->get_value(); + beats_edit->set_max(beat_max); + if (current_beat > beat_max) { + beats_edit->set_value(beat_max); + stream->call("set_beat_count", beat_max); + } + } + stream->call("set_bar_beats", bar_beats_edit->get_value()); + } else { + stream->call("set_bpm", 0); + stream->call("set_bar_beats", 4); + beats_enabled->hide(); + beats_edit->hide(); + bar_beats_label->hide(); + bar_beats_edit->hide(); + } + if (bpm_enabled->is_pressed() && beats_enabled->is_pressed()) { + stream->call("set_beat_count", beats_edit->get_value()); + } else { + stream->call("set_beat_count", 0); + } + + updating_settings = false; + + _preview->update(); + _indicator->update(); + color_rect->update(); +} + +void AudioStreamImportSettings::_reimport() { + params["loop"] = loop->is_pressed(); + params["loop_offset"] = loop_offset->get_value(); + params["bpm"] = bpm_enabled->is_pressed() ? double(bpm_edit->get_value()) : double(0); + params["beat_count"] = (bpm_enabled->is_pressed() && beats_enabled->is_pressed()) ? int(beats_edit->get_value()) : int(0); + params["bar_beats"] = (bpm_enabled->is_pressed()) ? int(bar_beats_edit->get_value()) : int(4); + + EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(path, importer, params); +} + +AudioStreamImportSettings::AudioStreamImportSettings() { + get_ok_button()->set_text(TTR("Reimport")); + get_cancel_button()->set_text(TTR("Close")); + + VBoxContainer *main_vbox = memnew(VBoxContainer); + add_child(main_vbox); + + HBoxContainer *loop_hb = memnew(HBoxContainer); + loop_hb->add_theme_constant_override("separation", 4 * EDSCALE); + loop = memnew(CheckBox); + loop->set_text(TTR("Enable")); + loop->set_tooltip(TTR("Enable looping.")); + loop->connect("toggled", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + loop_hb->add_child(loop); + loop_hb->add_spacer(); + loop_hb->add_child(memnew(Label(TTR("Offset:")))); + loop_offset = memnew(SpinBox); + loop_offset->set_max(10000); + loop_offset->set_step(0.001); + loop_offset->set_suffix("sec"); + loop_offset->set_tooltip(TTR("Loop offset (from beginning). Note that if BPM is set, this setting will be ignored.")); + loop_offset->connect("value_changed", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + loop_hb->add_child(loop_offset); + main_vbox->add_margin_child(TTR("Loop:"), loop_hb); + + HBoxContainer *interactive_hb = memnew(HBoxContainer); + interactive_hb->add_theme_constant_override("separation", 4 * EDSCALE); + bpm_enabled = memnew(CheckBox); + bpm_enabled->set_text((TTR("BPM:"))); + bpm_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + interactive_hb->add_child(bpm_enabled); + bpm_edit = memnew(SpinBox); + bpm_edit->set_max(400); + bpm_edit->set_step(0.01); + bpm_edit->set_tooltip(TTR("Configure the Beats Per Measure (tempo) used for the interactive streams.\nThis is required in order to configure beat information.")); + bpm_edit->connect("value_changed", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + interactive_hb->add_child(bpm_edit); + interactive_hb->add_spacer(); + bar_beats_label = memnew(Label(TTR("Beats/Bar:"))); + interactive_hb->add_child(bar_beats_label); + bar_beats_edit = memnew(SpinBox); + bar_beats_edit->set_tooltip(TTR("Configure the Beats Per Bar. This used for music-aware transitions between AudioStreams.")); + bar_beats_edit->set_min(2); + bar_beats_edit->set_max(32); + bar_beats_edit->connect("value_changed", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + interactive_hb->add_child(bar_beats_edit); + interactive_hb->add_spacer(); + beats_enabled = memnew(CheckBox); + beats_enabled->set_text(TTR("Length (in beats):")); + beats_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + interactive_hb->add_child(beats_enabled); + beats_edit = memnew(SpinBox); + beats_edit->set_tooltip(TTR("Configure the amount of Beats used for music-aware looping. If zero, it will be autodetected from the length.\nIt is recommended to set this value (either manually or by clicking on a beat number in the preview) to ensure looping works properly.")); + beats_edit->set_max(99999); + beats_edit->connect("value_changed", callable_mp(this, &AudioStreamImportSettings::_settings_changed).unbind(1)); + interactive_hb->add_child(beats_edit); + main_vbox->add_margin_child(TTR("Music Playback:"), interactive_hb); + + color_rect = memnew(ColorRect); + main_vbox->add_margin_child(TTR("Preview:"), color_rect); + + color_rect->set_custom_minimum_size(Size2(600, 200) * EDSCALE); + color_rect->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + _player = memnew(AudioStreamPlayer); + _player->connect("finished", callable_mp(this, &AudioStreamImportSettings::_on_finished)); + color_rect->add_child(_player); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + color_rect->add_child(vbox); + vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + _preview = memnew(ColorRect); + _preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + _preview->connect("draw", callable_mp(this, &AudioStreamImportSettings::_draw_preview)); + _preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vbox->add_child(_preview); + + HBoxContainer *zoom_hbox = memnew(HBoxContainer); + zoom_bar = memnew(HScrollBar); + zoom_in = memnew(Button); + zoom_in->set_flat(true); + zoom_reset = memnew(Button); + zoom_reset->set_flat(true); + zoom_out = memnew(Button); + zoom_out->set_flat(true); + zoom_hbox->add_child(zoom_bar); + zoom_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL); + zoom_bar->set_v_size_flags(Control::SIZE_EXPAND_FILL); + zoom_hbox->add_child(zoom_out); + zoom_hbox->add_child(zoom_reset); + zoom_hbox->add_child(zoom_in); + zoom_in->connect("pressed", callable_mp(this, &AudioStreamImportSettings::_preview_zoom_in)); + zoom_reset->connect("pressed", callable_mp(this, &AudioStreamImportSettings::_preview_zoom_reset)); + zoom_out->connect("pressed", callable_mp(this, &AudioStreamImportSettings::_preview_zoom_out)); + zoom_bar->connect("value_changed", callable_mp(this, &AudioStreamImportSettings::_preview_zoom_offset_changed)); + vbox->add_child(zoom_hbox); + + _indicator = memnew(Control); + _indicator->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + _indicator->connect("draw", callable_mp(this, &AudioStreamImportSettings::_draw_indicator)); + _indicator->connect("gui_input", callable_mp(this, &AudioStreamImportSettings::_on_input_indicator)); + _indicator->connect("mouse_exited", callable_mp(this, &AudioStreamImportSettings::_on_indicator_mouse_exited)); + _preview->add_child(_indicator); + + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->add_theme_constant_override("separation", 0); + vbox->add_child(hbox); + + _play_button = memnew(Button); + _play_button->set_flat(true); + hbox->add_child(_play_button); + _play_button->set_focus_mode(Control::FOCUS_NONE); + _play_button->connect("pressed", callable_mp(this, &AudioStreamImportSettings::_play)); + + _stop_button = memnew(Button); + _stop_button->set_flat(true); + hbox->add_child(_stop_button); + _stop_button->set_focus_mode(Control::FOCUS_NONE); + _stop_button->connect("pressed", callable_mp(this, &AudioStreamImportSettings::_stop)); + + _current_label = memnew(Label); + _current_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + _current_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + _current_label->set_modulate(Color(1, 1, 1, 0.5)); + hbox->add_child(_current_label); + + _duration_label = memnew(Label); + hbox->add_child(_duration_label); + + singleton = this; +} diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/import/audio_stream_import_settings.h index 0d927bddd5..5e399237ca 100644 --- a/editor/plugins/audio_stream_editor_plugin.h +++ b/editor/import/audio_stream_import_settings.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* audio_stream_editor_plugin.h */ +/* audio_stream_import_settings.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,17 +28,27 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef AUDIO_STREAM_EDITOR_PLUGIN_H -#define AUDIO_STREAM_EDITOR_PLUGIN_H +#ifndef AUDIO_STREAM_IMPORT_SETTINGS_H +#define AUDIO_STREAM_IMPORT_SETTINGS_H #include "editor/editor_plugin.h" #include "scene/audio/audio_stream_player.h" #include "scene/gui/color_rect.h" +#include "scene/gui/spin_box.h" #include "scene/resources/texture.h" -class AudioStreamEditor : public ColorRect { - GDCLASS(AudioStreamEditor, ColorRect); +class AudioStreamImportSettings : public ConfirmationDialog { + GDCLASS(AudioStreamImportSettings, ConfirmationDialog); + CheckBox *bpm_enabled = nullptr; + SpinBox *bpm_edit = nullptr; + CheckBox *beats_enabled = nullptr; + SpinBox *beats_edit = nullptr; + Label *bar_beats_label = nullptr; + SpinBox *bar_beats_edit = nullptr; + CheckBox *loop = nullptr; + SpinBox *loop_offset = nullptr; + ColorRect *color_rect = nullptr; Ref<AudioStream> stream; AudioStreamPlayer *_player = nullptr; ColorRect *_preview = nullptr; @@ -46,18 +56,42 @@ class AudioStreamEditor : public ColorRect { Label *_current_label = nullptr; Label *_duration_label = nullptr; + HScrollBar *zoom_bar = nullptr; + Button *zoom_in = nullptr; + Button *zoom_reset = nullptr; + Button *zoom_out = nullptr; + Button *_play_button = nullptr; Button *_stop_button = nullptr; + bool updating_settings = false; + float _current = 0; bool _dragging = false; + bool _beat_len_dragging = false; bool _pausing = false; + int _hovering_beat = -1; + + HashMap<StringName, Variant> params; + String importer; + String path; void _audio_changed(); + static AudioStreamImportSettings *singleton; + + void _settings_changed(); + + void _reimport(); + protected: void _notification(int p_what); void _preview_changed(ObjectID p_which); + void _preview_zoom_in(); + void _preview_zoom_out(); + void _preview_zoom_reset(); + void _preview_zoom_offset_changed(double); + void _play(); void _stop(); void _on_finished(); @@ -65,27 +99,16 @@ protected: void _draw_indicator(); void _on_input_indicator(Ref<InputEvent> p_event); void _seek_to(real_t p_x); - static void _bind_methods(); + void _set_beat_len_to(real_t p_x); + void _on_indicator_mouse_exited(); + int _get_beat_at_pos(real_t p_x); public: - void edit(Ref<AudioStream> p_stream); - AudioStreamEditor(); -}; + void edit(const String &p_path, const String &p_importer, const Ref<AudioStream> &p_stream); -class AudioStreamEditorPlugin : public EditorPlugin { - GDCLASS(AudioStreamEditorPlugin, EditorPlugin); + static AudioStreamImportSettings *get_singleton() { return singleton; } - AudioStreamEditor *audio_editor = nullptr; - -public: - virtual String get_name() const override { return "Audio"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - AudioStreamEditorPlugin(); - ~AudioStreamEditorPlugin(); + AudioStreamImportSettings(); }; -#endif // AUDIO_STREAM_EDITOR_PLUGIN_H +#endif // AUDIO_STREAM_IMPORT_SETTINGS_H diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp deleted file mode 100644 index 9b874ada45..0000000000 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/*************************************************************************/ -/* audio_stream_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "audio_stream_editor_plugin.h" - -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "core/os/keyboard.h" -#include "editor/audio_stream_preview.h" -#include "editor/editor_node.h" -#include "editor/editor_scale.h" -#include "editor/editor_settings.h" - -void AudioStreamEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { - AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AudioStreamEditor::_preview_changed)); - } break; - - case NOTIFICATION_THEME_CHANGED: - case NOTIFICATION_ENTER_TREE: { - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - _stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); - _preview->set_color(get_theme_color(SNAME("dark_color_2"), SNAME("Editor"))); - set_color(get_theme_color(SNAME("dark_color_1"), SNAME("Editor"))); - - _indicator->update(); - _preview->update(); - } break; - - case NOTIFICATION_PROCESS: { - _current = _player->get_playback_position(); - _indicator->update(); - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible_in_tree()) { - _stop(); - } - } break; - } -} - -void AudioStreamEditor::_draw_preview() { - Rect2 rect = _preview->get_rect(); - Size2 size = get_size(); - - Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); - float preview_len = preview->get_length(); - - Vector<Vector2> lines; - lines.resize(size.width * 2); - - for (int i = 0; i < size.width; i++) { - float ofs = i * preview_len / size.width; - float ofs_n = (i + 1) * preview_len / size.width; - float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; - float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; - - int idx = i; - lines.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y); - lines.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y); - } - - Vector<Color> color; - color.push_back(get_theme_color(SNAME("contrast_color_2"), SNAME("Editor"))); - - RS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), lines, color); -} - -void AudioStreamEditor::_preview_changed(ObjectID p_which) { - if (stream.is_valid() && stream->get_instance_id() == p_which) { - _preview->update(); - } -} - -void AudioStreamEditor::_audio_changed() { - if (!is_visible()) { - return; - } - update(); -} - -void AudioStreamEditor::_play() { - if (_player->is_playing()) { - // '_pausing' variable indicates that we want to pause the audio player, not stop it. See '_on_finished()'. - _pausing = true; - _player->stop(); - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - set_process(false); - } else { - _player->play(_current); - _play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); - set_process(true); - } -} - -void AudioStreamEditor::_stop() { - _player->stop(); - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - _current = 0; - _indicator->update(); - set_process(false); -} - -void AudioStreamEditor::_on_finished() { - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - if (!_pausing) { - _current = 0; - _indicator->update(); - } else { - _pausing = false; - } - set_process(false); -} - -void AudioStreamEditor::_draw_indicator() { - if (!stream.is_valid()) { - return; - } - - Rect2 rect = _preview->get_rect(); - float len = stream->get_length(); - float ofs_x = _current / len * rect.size.width; - const Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), color, Math::round(2 * EDSCALE)); - _indicator->draw_texture( - get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons")), - Point2(ofs_x - get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons"))->get_width() * 0.5, 0), - color); - - _current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /"); -} - -void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) { - const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { - if (mb->is_pressed()) { - _seek_to(mb->get_position().x); - } - _dragging = mb->is_pressed(); - } - - const Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { - if (_dragging) { - _seek_to(mm->get_position().x); - } - } -} - -void AudioStreamEditor::_seek_to(real_t p_x) { - _current = p_x / _preview->get_rect().size.x * stream->get_length(); - _current = CLAMP(_current, 0, stream->get_length()); - _player->seek(_current); - _indicator->update(); -} - -void AudioStreamEditor::edit(Ref<AudioStream> p_stream) { - if (!stream.is_null()) { - stream->disconnect("changed", callable_mp(this, &AudioStreamEditor::_audio_changed)); - } - - stream = p_stream; - _player->set_stream(stream); - _current = 0; - String text = String::num(stream->get_length(), 2).pad_decimals(2) + "s"; - _duration_label->set_text(text); - - if (!stream.is_null()) { - stream->connect("changed", callable_mp(this, &AudioStreamEditor::_audio_changed)); - update(); - } else { - hide(); - } -} - -void AudioStreamEditor::_bind_methods() { -} - -AudioStreamEditor::AudioStreamEditor() { - set_custom_minimum_size(Size2(1, 100) * EDSCALE); - - _player = memnew(AudioStreamPlayer); - _player->connect("finished", callable_mp(this, &AudioStreamEditor::_on_finished)); - add_child(_player); - - VBoxContainer *vbox = memnew(VBoxContainer); - vbox->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_MINSIZE, 0); - add_child(vbox); - - _preview = memnew(ColorRect); - _preview->set_v_size_flags(SIZE_EXPAND_FILL); - _preview->connect("draw", callable_mp(this, &AudioStreamEditor::_draw_preview)); - vbox->add_child(_preview); - - _indicator = memnew(Control); - _indicator->set_anchors_and_offsets_preset(PRESET_FULL_RECT); - _indicator->connect("draw", callable_mp(this, &AudioStreamEditor::_draw_indicator)); - _indicator->connect("gui_input", callable_mp(this, &AudioStreamEditor::_on_input_indicator)); - _preview->add_child(_indicator); - - HBoxContainer *hbox = memnew(HBoxContainer); - hbox->add_theme_constant_override("separation", 0); - vbox->add_child(hbox); - - _play_button = memnew(Button); - _play_button->set_flat(true); - hbox->add_child(_play_button); - _play_button->set_focus_mode(Control::FOCUS_NONE); - _play_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_play)); - _play_button->set_shortcut(ED_SHORTCUT("inspector/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), Key::SPACE)); - - _stop_button = memnew(Button); - _stop_button->set_flat(true); - hbox->add_child(_stop_button); - _stop_button->set_focus_mode(Control::FOCUS_NONE); - _stop_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_stop)); - - _current_label = memnew(Label); - _current_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - _current_label->set_h_size_flags(SIZE_EXPAND_FILL); - _current_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); - _current_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); - _current_label->set_modulate(Color(1, 1, 1, 0.5)); - hbox->add_child(_current_label); - - _duration_label = memnew(Label); - _duration_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); - _duration_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); - hbox->add_child(_duration_label); -} - -void AudioStreamEditorPlugin::edit(Object *p_object) { - AudioStream *s = Object::cast_to<AudioStream>(p_object); - if (!s) { - return; - } - - audio_editor->edit(Ref<AudioStream>(s)); -} - -bool AudioStreamEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("AudioStream"); -} - -void AudioStreamEditorPlugin::make_visible(bool p_visible) { - audio_editor->set_visible(p_visible); -} - -AudioStreamEditorPlugin::AudioStreamEditorPlugin() { - audio_editor = memnew(AudioStreamEditor); - add_control_to_container(CONTAINER_PROPERTY_EDITOR_BOTTOM, audio_editor); - audio_editor->hide(); -} - -AudioStreamEditorPlugin::~AudioStreamEditorPlugin() { -} diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 478f4264e5..6b632101d3 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -599,7 +599,7 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_f uint8_t *imgdata = img.ptrw(); uint8_t *imgw = imgdata; - Ref<AudioStreamPlayback> playback = stream->instance_playback(); + Ref<AudioStreamPlayback> playback = stream->instantiate_playback(); ERR_FAIL_COND_V(playback.is_null(), Ref<Texture2D>()); real_t len_s = stream->get_length(); diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 1386f03662..5c7047a81f 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -85,6 +85,7 @@ void GradientEditor::reverse_gradient() { } GradientEditor::GradientEditor() { + GradientEdit::get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(GradientEdit::get_picker())); editing = false; } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 6543da8776..df17e2747f 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -8028,6 +8028,7 @@ void fragment() { sun_color->set_edit_alpha(false); sun_vb->add_margin_child(TTR("Sun Color"), sun_color); sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + sun_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(sun_color->get_picker())); sun_energy = memnew(EditorSpinSlider); sun_vb->add_margin_child(TTR("Sun Energy"), sun_energy); @@ -8073,10 +8074,12 @@ void fragment() { environ_sky_color = memnew(ColorPickerButton); environ_sky_color->set_edit_alpha(false); environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + environ_sky_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(environ_sky_color->get_picker())); environ_vb->add_margin_child(TTR("Sky Color"), environ_sky_color); environ_ground_color = memnew(ColorPickerButton); environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); environ_ground_color->set_edit_alpha(false); + environ_ground_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(environ_ground_color->get_picker())); environ_vb->add_margin_child(TTR("Ground Color"), environ_ground_color); environ_energy = memnew(EditorSpinSlider); environ_energy->connect("value_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index fc545b44e8..14e3eb5402 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1862,16 +1862,10 @@ void ScriptTextEditor::_enable_code_editor() { color_picker = memnew(ColorPicker); color_picker->set_deferred_mode(true); color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_color_changed)); + color_panel->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(color_picker)); color_panel->add_child(color_picker); - // get default color picker mode from editor settings - int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode"); - color_picker->set_color_mode((ColorPicker::ColorModeType)default_color_mode); - - int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); - color_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); - quick_open = memnew(ScriptEditorQuickOpen); quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line)); add_child(quick_open); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 70b8c3aaa7..303b52c495 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -45,6 +45,7 @@ #include "editor/shader_create_dialog.h" #include "scene/gui/split_container.h" #include "servers/display_server.h" +#include "servers/rendering/shader_preprocessor.h" #include "servers/rendering/shader_types.h" /*** SHADER SCRIPT EDITOR ****/ @@ -72,15 +73,65 @@ Ref<Shader> ShaderTextEditor::get_edited_shader() const { return shader; } +Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const { + return shader_inc; +} + void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { + set_edited_shader(p_shader, p_shader->get_code()); +} + +void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) { if (shader == p_shader) { return; } + if (shader.is_valid()) { + shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + set_edited_code(p_code); + + if (shader.is_valid()) { + shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) { + set_edited_shader_include(p_shader_inc, p_shader_inc->get_code()); +} + +void ShaderTextEditor::_shader_changed() { + // This function is used for dependencies (include changing changes main shader and forces it to revalidate) + if (block_shader_changed) { + return; + } + dependencies_version++; + _validate_script(); +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) { + if (shader_inc == p_shader_inc) { + return; + } + if (shader_inc.is_valid()) { + shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + set_edited_code(p_code); + if (shader_inc.is_valid()) { + shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_code(const String &p_code) { _load_theme_settings(); - get_text_editor()->set_text(p_shader->get_code()); + get_text_editor()->set_text(p_code); get_text_editor()->clear_undo_history(); get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0); get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0); @@ -132,11 +183,12 @@ void ShaderTextEditor::_load_theme_settings() { syntax_highlighter->clear_keyword_colors(); - List<String> keywords; - ShaderLanguage::get_keyword_list(&keywords); const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); + List<String> keywords; + ShaderLanguage::get_keyword_list(&keywords); + for (const String &E : keywords) { if (ShaderLanguage::is_control_flow_keyword(E)) { syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); @@ -145,6 +197,13 @@ void ShaderTextEditor::_load_theme_settings() { } } + List<String> pp_keywords; + ShaderPreprocessor::get_keyword_list(&pp_keywords, false); + + for (const String &E : pp_keywords) { + syntax_highlighter->add_keyword_color(E, keyword_color); + } + // Colorize built-ins like `COLOR` differently to make them easier // to distinguish from keywords at a quick glance. @@ -191,8 +250,12 @@ void ShaderTextEditor::_load_theme_settings() { text_editor->add_auto_brace_completion_pair("/*", "*/"); } + // Colorize preprocessor include strings. + const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + syntax_highlighter->add_color_region("\"", "\"", string_color, false); + if (warnings_panel) { - // Warnings panel + // Warnings panel. warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); } @@ -216,7 +279,9 @@ void ShaderTextEditor::_check_shader_mode() { } if (shader->get_mode() != mode) { + set_block_shader_changed(true); shader->set_code(get_text_editor()->get_text()); + set_block_shader_changed(false); _load_theme_settings(); } } @@ -226,72 +291,193 @@ static ShaderLanguage::DataType _get_global_variable_type(const StringName &p_va return (ShaderLanguage::DataType)RS::global_variable_type_get_shader_datatype(gvt); } +static String complete_from_path; + +static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) { + if (!p_efsd) { + return; + } + for (int i = 0; i < p_efsd->get_file_count(); i++) { + if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) { + String path = p_efsd->get_file_path(i); + if (path.begins_with(complete_from_path)) { + path = path.replace_first(complete_from_path, ""); + } + r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH)); + } + } + for (int j = 0; j < p_efsd->get_subdir_count(); j++) { + _complete_include_paths_search(p_efsd->get_subdir(j), r_options); + } +} + +static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) { + _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options); +} + void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) { - _check_shader_mode(); + List<ScriptLanguage::CodeCompletionOption> pp_options; + ShaderPreprocessor preprocessor; + String code; + complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir(); + if (!complete_from_path.ends_with("/")) { + complete_from_path += "/"; + } + preprocessor.preprocess(p_code, code, nullptr, nullptr, nullptr, &pp_options, _complete_include_paths); + complete_from_path = String(); + if (pp_options.size()) { + for (const ScriptLanguage::CodeCompletionOption &E : pp_options) { + r_options->push_back(E); + } + return; + } ShaderLanguage sl; String calltip; - ShaderLanguage::ShaderCompileInfo info; + info.global_variable_type_func = _get_global_variable_type; + + Ref<ShaderInclude> inc = shader_inc; + if (shader.is_null()) { + info.is_include = true; + + sl.complete(p_code, info, r_options, calltip); + get_text_editor()->set_code_hint(calltip); + return; + } + _check_shader_mode(); info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); info.shader_types = ShaderTypes::get_singleton()->get_types(); - info.global_variable_type_func = _get_global_variable_type; sl.complete(p_code, info, r_options, calltip); - get_text_editor()->set_code_hint(calltip); } void ShaderTextEditor::_validate_script() { - _check_shader_mode(); + emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied - String code = get_text_editor()->get_text(); - //List<StringName> params; - //shader->get_param_list(¶ms); + String code; - ShaderLanguage::ShaderCompileInfo info; - info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); - info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - info.shader_types = ShaderTypes::get_singleton()->get_types(); - info.global_variable_type_func = _get_global_variable_type; - - ShaderLanguage sl; + if (shader.is_valid()) { + _check_shader_mode(); + code = shader->get_code(); + } else { + code = shader_inc->get_code(); + } - sl.enable_warning_checking(saved_warnings_enabled); - sl.set_warning_flags(saved_warning_flags); + ShaderPreprocessor preprocessor; + String code_pp; + String error_pp; + List<ShaderPreprocessor::FilePosition> err_positions; + last_compile_result = preprocessor.preprocess(code, code_pp, &error_pp, &err_positions); - last_compile_result = sl.compile(code, info); + for (int i = 0; i < get_text_editor()->get_line_count(); i++) { + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + } + set_error(""); + set_error_count(0); if (last_compile_result != OK) { - String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); + //preprocessor error + ERR_FAIL_COND(err_positions.size() == 0); + + String error_text = error_pp; + int error_line = err_positions.front()->get().line; + if (err_positions.size() == 1) { + // Error in main file + error_text = "error(" + itos(error_line) + "): " + error_text; + } else { + error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text; + set_error_count(err_positions.size() - 1); + } + set_error(error_text); - set_error_pos(sl.get_error_line() - 1, 0); + set_error_pos(error_line - 1, 0); for (int i = 0; i < get_text_editor()->get_line_count(); i++) { get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); } - get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color); + get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); + + set_warning_count(0); + } else { - for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + ShaderLanguage sl; + + sl.enable_warning_checking(saved_warnings_enabled); + uint32_t flags = saved_warning_flags; + if (shader.is_null()) { + if (flags & ShaderWarning::UNUSED_CONSTANT) { + flags &= ~(ShaderWarning::UNUSED_CONSTANT); + } + if (flags & ShaderWarning::UNUSED_FUNCTION) { + flags &= ~(ShaderWarning::UNUSED_FUNCTION); + } + if (flags & ShaderWarning::UNUSED_STRUCT) { + flags &= ~(ShaderWarning::UNUSED_STRUCT); + } + if (flags & ShaderWarning::UNUSED_UNIFORM) { + flags &= ~(ShaderWarning::UNUSED_UNIFORM); + } + if (flags & ShaderWarning::UNUSED_VARYING) { + flags &= ~(ShaderWarning::UNUSED_VARYING); + } } - set_error(""); - } + sl.set_warning_flags(flags); - if (warnings.size() > 0 || last_compile_result != OK) { - warnings_panel->clear(); - } - warnings.clear(); - for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { - warnings.push_back(E->get()); - } - if (warnings.size() > 0 && last_compile_result == OK) { - warnings.sort_custom<WarningsComparator>(); - _update_warning_panel(); - } else { - set_warning_count(0); + ShaderLanguage::ShaderCompileInfo info; + info.global_variable_type_func = _get_global_variable_type; + + if (shader.is_null()) { + info.is_include = true; + } else { + Shader::Mode mode = shader->get_mode(); + info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode)); + info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode)); + info.shader_types = ShaderTypes::get_singleton()->get_types(); + } + + code = code_pp; + //compiler error + last_compile_result = sl.compile(code, info); + + if (last_compile_result != OK) { + String error_text; + int error_line; + Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions(); + if (include_positions.size() > 1) { + //error is in an include + error_line = include_positions[0].line; + error_text = "error(" + itos(error_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text(); + set_error_count(include_positions.size() - 1); + } else { + error_line = sl.get_error_line(); + error_text = "error(" + itos(error_line) + "): " + sl.get_error_text(); + set_error_count(0); + } + set_error(error_text); + set_error_pos(error_line - 1, 0); + get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); + } else { + set_error(""); + } + + if (warnings.size() > 0 || last_compile_result != OK) { + warnings_panel->clear(); + } + warnings.clear(); + for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { + warnings.push_back(E->get()); + } + if (warnings.size() > 0 && last_compile_result == OK) { + warnings.sort_custom<WarningsComparator>(); + _update_warning_panel(); + } else { + set_warning_count(0); + } } - emit_signal(SNAME("script_changed")); + + emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts } void ShaderTextEditor::_update_warning_panel() { @@ -338,6 +524,7 @@ void ShaderTextEditor::_update_warning_panel() { } void ShaderTextEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid"))); } ShaderTextEditor::ShaderTextEditor() { @@ -473,6 +660,8 @@ void ShaderEditor::_warning_clicked(Variant p_line) { void ShaderEditor::_bind_methods() { ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel); ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked); + + ADD_SIGNAL(MethodInfo("validation_changed")); } void ShaderEditor::ensure_select_current() { @@ -524,15 +713,23 @@ void ShaderEditor::_update_warnings(bool p_validate) { } void ShaderEditor::_check_for_external_edit() { - if (shader.is_null() || !shader.is_valid()) { + bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); + + if (shader_inc.is_valid()) { + if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) { + if (use_autoreload) { + _reload_shader_include_from_disk(); + } else { + disk_changed->call_deferred(SNAME("popup_centered")); + } + } return; } - if (shader->is_built_in()) { + if (shader.is_null() || shader->is_built_in()) { return; } - bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { if (use_autoreload) { _reload_shader_from_disk(); @@ -546,11 +743,32 @@ void ShaderEditor::_reload_shader_from_disk() { Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); ERR_FAIL_COND(!rel_shader.is_valid()); + shader_editor->set_block_shader_changed(true); shader->set_code(rel_shader->get_code()); + shader_editor->set_block_shader_changed(false); shader->set_last_modified_time(rel_shader->get_last_modified_time()); shader_editor->reload_text(); } +void ShaderEditor::_reload_shader_include_from_disk() { + Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND(!rel_shader_include.is_valid()); + + shader_editor->set_block_shader_changed(true); + shader_inc->set_code(rel_shader_include->get_code()); + shader_editor->set_block_shader_changed(false); + shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time()); + shader_editor->reload_text(); +} + +void ShaderEditor::_reload() { + if (shader.is_valid()) { + _reload_shader_from_disk(); + } else if (shader_inc.is_valid()) { + _reload_shader_include_from_disk(); + } +} + void ShaderEditor::edit(const Ref<Shader> &p_shader) { if (p_shader.is_null() || !p_shader->is_text_shader()) { return; @@ -561,37 +779,79 @@ void ShaderEditor::edit(const Ref<Shader> &p_shader) { } shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + shader_editor->set_edited_shader(shader); +} - shader_editor->set_edited_shader(p_shader); +void ShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) { + if (p_shader_inc.is_null()) { + return; + } - //vertex_editor->set_edited_shader(shader,ShaderLanguage::SHADER_MATERIAL_VERTEX); - // see if already has it + if (shader_inc == p_shader_inc) { + return; + } + + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + shader_editor->set_edited_shader_include(p_shader_inc); } void ShaderEditor::save_external_data(const String &p_str) { - if (shader.is_null()) { + if (shader.is_null() && shader_inc.is_null()) { disk_changed->hide(); return; } apply_shaders(); - if (!shader->is_built_in()) { - //external shader, save it + + Ref<Shader> edited_shader = shader_editor->get_edited_shader(); + if (edited_shader.is_valid()) { + ResourceSaver::save(edited_shader->get_path(), edited_shader); + } + if (shader.is_valid() && shader != edited_shader) { ResourceSaver::save(shader->get_path(), shader); } + Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include(); + if (edited_shader_inc.is_valid()) { + ResourceSaver::save(edited_shader_inc->get_path(), edited_shader_inc); + } + if (shader_inc.is_valid() && shader_inc != edited_shader_inc) { + ResourceSaver::save(shader_inc->get_path(), shader_inc); + } + disk_changed->hide(); } +void ShaderEditor::validate_script() { + shader_editor->_validate_script(); +} + void ShaderEditor::apply_shaders() { + String editor_code = shader_editor->get_text_editor()->get_text(); if (shader.is_valid()) { String shader_code = shader->get_code(); - String editor_code = shader_editor->get_text_editor()->get_text(); - if (shader_code != editor_code) { + if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { + shader_editor->set_block_shader_changed(true); shader->set_code(editor_code); + shader_editor->set_block_shader_changed(false); shader->set_edited(true); } } + if (shader_inc.is_valid()) { + String shader_inc_code = shader_inc->get_code(); + if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { + shader_editor->set_block_shader_changed(true); + shader_inc->set_code(editor_code); + shader_editor->set_block_shader_changed(false); + shader_inc->set_edited(true); + } + } + + dependencies_version = shader_editor->get_dependencies_version(); } void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { @@ -704,6 +964,9 @@ ShaderEditor::ShaderEditor() { _update_warnings(false); shader_editor = memnew(ShaderTextEditor); + + shader_editor->connect("script_validated", callable_mp(this, &ShaderEditor::_script_validated)); + shader_editor->set_v_size_flags(SIZE_EXPAND_FILL); shader_editor->add_theme_constant_override("separation", 0); shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); @@ -829,7 +1092,7 @@ ShaderEditor::ShaderEditor() { dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?")); vbc->add_child(dl); - disk_changed->connect("confirmed", callable_mp(this, &ShaderEditor::_reload_shader_from_disk)); + disk_changed->connect("confirmed", callable_mp(this, &ShaderEditor::_reload)); disk_changed->set_ok_button_text(TTR("Reload")); disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); @@ -844,19 +1107,37 @@ void ShaderEditorPlugin::_update_shader_list() { shader_list->clear(); for (uint32_t i = 0; i < edited_shaders.size(); i++) { String text; - String path = edited_shaders[i].shader->get_path(); - String _class = edited_shaders[i].shader->get_class(); + String path; + String _class; + String shader_name; + if (edited_shaders[i].shader.is_valid()) { + Ref<Shader> shader = edited_shaders[i].shader; + + path = shader->get_path(); + _class = shader->get_class(); + shader_name = shader->get_name(); + } else { + Ref<ShaderInclude> shader_inc = edited_shaders[i].shader_inc; + + path = shader_inc->get_path(); + _class = shader_inc->get_class(); + shader_name = shader_inc->get_name(); + } if (path.is_resource_file()) { text = path.get_file(); - } else if (edited_shaders[i].shader->get_name() != "") { - text = edited_shaders[i].shader->get_name(); + } else if (shader_name != "") { + text = shader_name; } else { - text = _class + ":" + itos(edited_shaders[i].shader->get_instance_id()); + if (edited_shaders[i].shader.is_valid()) { + text = _class + ":" + itos(edited_shaders[i].shader->get_instance_id()); + } else { + text = _class + ":" + itos(edited_shaders[i].shader_inc->get_instance_id()); + } } if (!shader_list->has_theme_icon(_class, SNAME("EditorIcons"))) { - _class = "Resource"; + _class = "TextFile"; } Ref<Texture2D> icon = shader_list->get_theme_icon(_class, SNAME("EditorIcons")); @@ -871,38 +1152,70 @@ void ShaderEditorPlugin::_update_shader_list() { for (int i = 1; i < FILE_MAX; i++) { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.size() == 0); } + + _update_shader_list_status(); } -void ShaderEditorPlugin::edit(Object *p_object) { - Shader *s = Object::cast_to<Shader>(p_object); - for (uint32_t i = 0; i < edited_shaders.size(); i++) { - if (edited_shaders[i].shader.ptr() == s) { - // Exists, select. - shader_tabs->set_current_tab(i); - shader_list->select(i); - return; +void ShaderEditorPlugin::_update_shader_list_status() { + for (int i = 0; i < shader_list->get_item_count(); i++) { + ShaderEditor *se = Object::cast_to<ShaderEditor>(shader_tabs->get_tab_control(i)); + if (se) { + if (se->was_compilation_successful()) { + shader_list->set_item_tag_icon(i, Ref<Texture2D>()); + } else { + shader_list->set_item_tag_icon(i, shader_list->get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); + } } } - // Add. +} + +void ShaderEditorPlugin::edit(Object *p_object) { EditedShader es; - es.shader = Ref<Shader>(s); - Ref<VisualShader> vs = es.shader; - if (vs.is_valid()) { - es.visual_shader_editor = memnew(VisualShaderEditor); - shader_tabs->add_child(es.visual_shader_editor); - es.visual_shader_editor->edit(vs.ptr()); - } else { + + ShaderInclude *si = Object::cast_to<ShaderInclude>(p_object); + if (si != nullptr) { + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader_inc.ptr() == si) { + shader_tabs->set_current_tab(i); + shader_list->select(i); + return; + } + } + es.shader_inc = Ref<ShaderInclude>(si); es.shader_editor = memnew(ShaderEditor); + es.shader_editor->edit(si); shader_tabs->add_child(es.shader_editor); - es.shader_editor->edit(s); + es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list_status)); + } else { + Shader *s = Object::cast_to<Shader>(p_object); + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader.ptr() == s) { + shader_tabs->set_current_tab(i); + shader_list->select(i); + return; + } + } + es.shader = Ref<Shader>(s); + Ref<VisualShader> vs = es.shader; + if (vs.is_valid()) { + es.visual_shader_editor = memnew(VisualShaderEditor); + shader_tabs->add_child(es.visual_shader_editor); + es.visual_shader_editor->edit(vs.ptr()); + } else { + es.shader_editor = memnew(ShaderEditor); + shader_tabs->add_child(es.shader_editor); + es.shader_editor->edit(s); + es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list_status)); + } } + shader_tabs->set_current_tab(shader_tabs->get_tab_count() - 1); edited_shaders.push_back(es); _update_shader_list(); } bool ShaderEditorPlugin::handles(Object *p_object) const { - return Object::cast_to<Shader>(p_object) != nullptr; + return Object::cast_to<Shader>(p_object) != nullptr || Object::cast_to<ShaderInclude>(p_object) != nullptr; } void ShaderEditorPlugin::make_visible(bool p_visible) { @@ -949,6 +1262,9 @@ void ShaderEditorPlugin::apply_changes() { } void ShaderEditorPlugin::_shader_selected(int p_index) { + if (edited_shaders[p_index].shader_editor) { + edited_shaders[p_index].shader_editor->validate_script(); + } shader_tabs->set_current_tab(p_index); } @@ -975,31 +1291,56 @@ void ShaderEditorPlugin::_resource_saved(Object *obj) { void ShaderEditorPlugin::_menu_item_pressed(int p_index) { switch (p_index) { case FILE_NEW: { - String base_path = FileSystemDock::get_singleton()->get_current_path(); + String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir(); shader_create_dialog->config(base_path.plus_file("new_shader"), false, false, 0); shader_create_dialog->popup_centered(); } break; + case FILE_NEW_INCLUDE: { + String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir(); + shader_create_dialog->config(base_path.plus_file("new_shader"), false, false, 2); + shader_create_dialog->popup_centered(); + } break; case FILE_OPEN: { InspectorDock::get_singleton()->open_resource("Shader"); } break; + case FILE_OPEN_INCLUDE: { + InspectorDock::get_singleton()->open_resource("ShaderInclude"); + } break; case FILE_SAVE: { int index = shader_tabs->get_current_tab(); ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); - EditorNode::get_singleton()->save_resource(edited_shaders[index].shader); + if (edited_shaders[index].shader.is_valid()) { + EditorNode::get_singleton()->save_resource(edited_shaders[index].shader); + } else { + EditorNode::get_singleton()->save_resource(edited_shaders[index].shader_inc); + } } break; case FILE_SAVE_AS: { int index = shader_tabs->get_current_tab(); ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); - String path = edited_shaders[index].shader->get_path(); - if (!path.is_resource_file()) { - path = ""; + String path; + if (edited_shaders[index].shader.is_valid()) { + path = edited_shaders[index].shader->get_path(); + if (!path.is_resource_file()) { + path = ""; + } + EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader, path); + } else { + path = edited_shaders[index].shader_inc->get_path(); + if (!path.is_resource_file()) { + path = ""; + } + EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader_inc, path); } - EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader, path); } break; case FILE_INSPECT: { int index = shader_tabs->get_current_tab(); ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); - EditorNode::get_singleton()->push_item(edited_shaders[index].shader.ptr()); + if (edited_shaders[index].shader.is_valid()) { + EditorNode::get_singleton()->push_item(edited_shaders[index].shader.ptr()); + } else { + EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr()); + } } break; case FILE_CLOSE: { _close_shader(shader_tabs->get_current_tab()); @@ -1011,6 +1352,10 @@ void ShaderEditorPlugin::_shader_created(Ref<Shader> p_shader) { EditorNode::get_singleton()->push_item(p_shader.ptr()); } +void ShaderEditorPlugin::_shader_include_created(Ref<ShaderInclude> p_shader_inc) { + EditorNode::get_singleton()->push_item(p_shader_inc.ptr()); +} + ShaderEditorPlugin::ShaderEditorPlugin() { main_split = memnew(HSplitContainer); @@ -1021,18 +1366,20 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW); + file_menu->get_popup()->add_item(TTR("New Shader Include"), FILE_NEW_INCLUDE); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_item(TTR("Load Shader"), FILE_OPEN); - file_menu->get_popup()->add_item(TTR("Save Shader"), FILE_SAVE); - file_menu->get_popup()->add_item(TTR("Save Shader As"), FILE_SAVE_AS); + file_menu->get_popup()->add_item(TTR("Load Shader File"), FILE_OPEN); + file_menu->get_popup()->add_item(TTR("Load Shader Include File"), FILE_OPEN_INCLUDE); + file_menu->get_popup()->add_item(TTR("Save File"), FILE_SAVE); + file_menu->get_popup()->add_item(TTR("Save File As"), FILE_SAVE_AS); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_item(TTR("Open Shader in Inspector"), FILE_INSPECT); + file_menu->get_popup()->add_item(TTR("Open File in Inspector"), FILE_INSPECT); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_item(TTR("Close Shader"), FILE_CLOSE); + file_menu->get_popup()->add_item(TTR("Close File"), FILE_CLOSE); file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed)); file_hb->add_child(file_menu); - for (int i = 1; i < FILE_MAX; i++) { + for (int i = 2; i < FILE_MAX; i++) { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); } @@ -1060,6 +1407,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { shader_create_dialog = memnew(ShaderCreateDialog); vb->add_child(shader_create_dialog); shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created)); + shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created)); } ShaderEditorPlugin::~ShaderEditorPlugin() { diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 2e0dbe0d60..221822e8f2 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -40,6 +40,7 @@ #include "scene/gui/text_edit.h" #include "scene/main/timer.h" #include "scene/resources/shader.h" +#include "scene/resources/shader_include.h" #include "servers/rendering/shader_warnings.h" class ItemList; @@ -59,12 +60,18 @@ class ShaderTextEditor : public CodeTextEditor { Ref<CodeHighlighter> syntax_highlighter; RichTextLabel *warnings_panel = nullptr; Ref<Shader> shader; + Ref<ShaderInclude> shader_inc; List<ShaderWarning> warnings; Error last_compile_result = Error::OK; void _check_shader_mode(); void _update_warning_panel(); + bool block_shader_changed = false; + void _shader_changed(); + + uint32_t dependencies_version = 0; // Incremented if deps changed + protected: void _notification(int p_what); static void _bind_methods(); @@ -73,13 +80,23 @@ protected: virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override; public: + void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; } + uint32_t get_dependencies_version() const { return dependencies_version; } + virtual void _validate_script() override; void reload_text(); void set_warnings_panel(RichTextLabel *p_warnings_panel); Ref<Shader> get_edited_shader() const; + Ref<ShaderInclude> get_edited_shader_include() const; + void set_edited_shader(const Ref<Shader> &p_shader); + void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code); + void set_edited_code(const String &p_code); + ShaderTextEditor(); }; @@ -126,38 +143,50 @@ class ShaderEditor : public PanelContainer { ConfirmationDialog *disk_changed = nullptr; ShaderTextEditor *shader_editor = nullptr; + bool compilation_success = true; void _menu_option(int p_option); mutable Ref<Shader> shader; + mutable Ref<ShaderInclude> shader_inc; void _editor_settings_changed(); void _project_settings_changed(); void _check_for_external_edit(); void _reload_shader_from_disk(); + void _reload_shader_include_from_disk(); + void _reload(); void _show_warnings_panel(bool p_show); void _warning_clicked(Variant p_line); void _update_warnings(bool p_validate); + void _script_validated(bool p_valid) { + compilation_success = p_valid; + emit_signal(SNAME("validation_changed")); + } + + uint32_t dependencies_version = 0xFFFFFFFF; + protected: void _notification(int p_what); static void _bind_methods(); void _make_context_menu(bool p_selection, Vector2 p_position); - void _text_edit_gui_input(const Ref<InputEvent> &ev); + void _text_edit_gui_input(const Ref<InputEvent> &p_ev); void _update_bookmark_list(); void _bookmark_item_pressed(int p_idx); public: + bool was_compilation_successful() const { return compilation_success; } void apply_shaders(); - void ensure_select_current(); void edit(const Ref<Shader> &p_shader); - + void edit(const Ref<ShaderInclude> &p_shader_inc); void goto_line_selection(int p_line, int p_begin, int p_end); + void save_external_data(const String &p_str = ""); + void validate_script(); virtual Size2 get_minimum_size() const override { return Size2(0, 200); } - void save_external_data(const String &p_str = ""); ShaderEditor(); }; @@ -167,6 +196,7 @@ class ShaderEditorPlugin : public EditorPlugin { struct EditedShader { Ref<Shader> shader; + Ref<ShaderInclude> shader_inc; ShaderEditor *shader_editor = nullptr; VisualShaderEditor *visual_shader_editor = nullptr; }; @@ -175,7 +205,9 @@ class ShaderEditorPlugin : public EditorPlugin { enum { FILE_NEW, + FILE_NEW_INCLUDE, FILE_OPEN, + FILE_OPEN_INCLUDE, FILE_SAVE, FILE_SAVE_AS, FILE_INSPECT, @@ -199,6 +231,8 @@ class ShaderEditorPlugin : public EditorPlugin { void _close_shader(int p_index); void _shader_created(Ref<Shader> p_shader); + void _shader_include_created(Ref<ShaderInclude> p_shader_inc); + void _update_shader_list_status(); public: virtual String get_name() const override { return "Shader"; } diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index afd29ae8e5..744ed1f1a2 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2492,6 +2492,7 @@ void ThemeTypeEditor::_update_type_items() { if (E.value) { item_editor->set_pick_color(edited_theme->get_color(E.key, edited_type)); item_editor->connect("color_changed", callable_mp(this, &ThemeTypeEditor::_color_item_changed), varray(E.key)); + item_editor->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(item_editor->get_picker())); } else { item_editor->set_pick_color(Theme::get_default()->get_color(E.key, edited_type)); item_editor->set_disabled(true); diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index 0693294316..488a084903 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -828,13 +828,7 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant:: value_vbox->add_child(color_picker); color_picker->hide(); color_picker->connect("color_changed", callable_mp(this, &CustomPropertyEditor::_color_changed)); - - // get default color picker mode from editor settings - int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode"); - color_picker->set_color_mode((ColorPicker::ColorModeType)default_color_mode); - - int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); - color_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); + color_picker->connect("show", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker), varray(color_picker)); } color_picker->show(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index be37d50a2e..8cf0f50db8 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -72,7 +72,7 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) { - if (mb->is_pressed() && scene_tree->get_rect().has_point(mb->get_position())) { + if (mb->is_pressed() && scene_tree->get_rect().has_point(scene_tree->get_local_mouse_position())) { tree_clicked = true; } else if (!mb->is_pressed()) { tree_clicked = false; diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 28e1e9bf22..7ae03ee96f 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "editor/editor_file_dialog.h" #include "editor/editor_scale.h" +#include "scene/resources/shader_include.h" #include "scene/resources/visual_shader.h" #include "servers/rendering/shader_types.h" @@ -43,15 +44,15 @@ void ShaderCreateDialog::_notification(int p_what) { 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; + for (int i = 0; i < type_menu->get_item_count(); i++) { + if (type_menu->get_item_text(i) == last_lang) { + type_menu->select(i); + current_type = i; break; } } } else { - language_menu->select(default_language); + type_menu->select(default_type); } current_mode = EditorSettings::get_singleton()->get_project_metadata("shader_setup", "last_selected_mode", 0); @@ -67,12 +68,17 @@ void ShaderCreateDialog::_notification(int p_what) { 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); + type_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); + type_menu->set_item_icon(1, visual_shader_icon); + } + + Ref<Texture2D> include_icon = gc->get_theme_icon(SNAME("TextFile"), SNAME("EditorIcons")); + if (include_icon.is_valid()) { + type_menu->set_item_icon(2, include_icon); } path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); @@ -80,7 +86,7 @@ void ShaderCreateDialog::_update_theme() { } void ShaderCreateDialog::_update_language_info() { - language_data.clear(); + type_data.clear(); for (int i = 0; i < SHADER_TYPE_MAX; i++) { ShaderTypeData data; @@ -88,12 +94,15 @@ void ShaderCreateDialog::_update_language_info() { data.use_templates = true; data.extensions.push_back("gdshader"); data.default_extension = "gdshader"; + } else if (i == int(SHADER_TYPE_INC)) { + data.extensions.push_back("gdshaderinc"); + data.default_extension = "gdshaderinc"; } else { data.default_extension = "tres"; } data.extensions.push_back("res"); data.extensions.push_back("tres"); - language_data.push_back(data); + type_data.push_back(data); } } @@ -136,69 +145,97 @@ void ShaderCreateDialog::ok_pressed() { void ShaderCreateDialog::_create_new() { Ref<Resource> 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; - case Shader::MODE_FOG: - code += "void fog() {\n"; - code += "\t// Place fog code here.\n"; - code += "}\n"; - break; + Ref<Resource> shader_inc; + + switch (type_menu->get_selected()) { + case 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; + case Shader::MODE_FOG: + code += "void fog() {\n"; + code += "\t// Place fog 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_mode(Shader::Mode(current_mode)); + text_shader->set_code(code.as_string()); + } break; + case SHADER_TYPE_VISUAL: { + Ref<VisualShader> visual_shader; + visual_shader.instantiate(); + shader = visual_shader; + visual_shader->set_mode(Shader::Mode(current_mode)); + } break; + case SHADER_TYPE_INC: { + Ref<ShaderInclude> include; + include.instantiate(); + shader_inc = include; + } break; + default: { + } break; } - if (!is_built_in) { + if (shader.is_null()) { 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.")); + shader_inc->set_path(lpath); + + Error error = ResourceSaver::save(lpath, shader_inc, ResourceSaver::FLAG_CHANGE_PATH); + if (error != OK) { + alert->set_text(TTR("Error - Could not create shader include in filesystem.")); alert->popup_centered(); return; } + + emit_signal(SNAME("shader_include_created"), shader_inc); + } else { + if (!is_built_in) { + String lpath = ProjectSettings::get_singleton()->localize_path(file_path->get_text()); + shader->set_path(lpath); + + Error error = ResourceSaver::save(lpath, shader, ResourceSaver::FLAG_CHANGE_PATH); + if (error != OK) { + alert->set_text(TTR("Error - Could not create shader in filesystem.")); + alert->popup_centered(); + return; + } + } + + emit_signal(SNAME("shader_created"), shader); } - emit_signal(SNAME("shader_created"), shader); + file_path->set_text(file_path->get_text().get_base_dir()); hide(); } @@ -215,9 +252,9 @@ void ShaderCreateDialog::_load_exist() { hide(); } -void ShaderCreateDialog::_language_changed(int p_language) { - current_language = p_language; - ShaderTypeData data = language_data[p_language]; +void ShaderCreateDialog::_type_changed(int p_language) { + current_type = p_language; + ShaderTypeData data = type_data[p_language]; String selected_ext = "." + data.default_extension; String path = file_path->get_text(); @@ -238,6 +275,8 @@ void ShaderCreateDialog::_language_changed(int p_language) { _path_changed(path); file_path->set_text(path); + type_menu->set_item_disabled(int(SHADER_TYPE_INC), load_enabled); + mode_menu->set_disabled(p_language == SHADER_TYPE_INC); template_menu->set_disabled(!data.use_templates); template_menu->clear(); @@ -253,7 +292,7 @@ void ShaderCreateDialog::_language_changed(int p_language) { 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())); + EditorSettings::get_singleton()->set_project_metadata("shader_setup", "last_selected_language", type_menu->get_item_text(type_menu->get_selected())); _update_dialog(); } @@ -275,7 +314,7 @@ void ShaderCreateDialog::_browse_path() { file_browse->set_disable_overwrite_warning(true); file_browse->clear_filters(); - List<String> extensions = language_data[language_menu->get_selected()].extensions; + List<String> extensions = type_data[type_menu->get_selected()].extensions; for (const String &E : extensions) { file_browse->add_filter("*." + E); @@ -330,8 +369,8 @@ void ShaderCreateDialog::_path_submitted(const String &p_path) { 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) { if (!p_base_path.is_empty()) { 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(); + file_path->set_text(initial_base_path + "." + type_data[type_menu->get_selected()].default_extension); + current_type = type_menu->get_selected(); } else { initial_base_path = ""; file_path->set_text(""); @@ -342,8 +381,8 @@ void ShaderCreateDialog::config(const String &p_base_path, bool p_built_in_enabl load_enabled = p_load_enabled; if (p_preferred_type > -1) { - language_menu->select(p_preferred_type); - _language_changed(p_preferred_type); + type_menu->select(p_preferred_type); + _type_changed(p_preferred_type); } if (p_preferred_mode > -1) { @@ -351,7 +390,7 @@ void ShaderCreateDialog::config(const String &p_base_path, bool p_built_in_enabl _mode_changed(p_preferred_mode); } - _language_changed(current_language); + _type_changed(current_type); _path_changed(file_path->get_text()); } @@ -384,14 +423,14 @@ String ShaderCreateDialog::_validate_path(const String &p_path) { HashSet<String> extensions; for (int i = 0; i < SHADER_TYPE_MAX; i++) { - for (const String &ext : language_data[i].extensions) { + for (const String &ext : type_data[i].extensions) { if (!extensions.has(ext)) { extensions.insert(ext); } } } - ShaderTypeData data = language_data[language_menu->get_selected()]; + ShaderTypeData data = type_data[type_menu->get_selected()]; bool found = false; bool match = false; @@ -399,8 +438,8 @@ String ShaderCreateDialog::_validate_path(const String &p_path) { for (const String &ext : extensions) { if (ext.nocasecmp_to(extension) == 0) { found = true; - for (const String &lang_ext : language_data[current_language].extensions) { - if (lang_ext.nocasecmp_to(extension) == 0) { + for (const String &type_ext : type_data[current_type].extensions) { + if (type_ext.nocasecmp_to(extension) == 0) { match = true; break; } @@ -504,6 +543,7 @@ 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"))); + ADD_SIGNAL(MethodInfo("shader_include_created", PropertyInfo(Variant::OBJECT, "shader_include", PROPERTY_HINT_RESOURCE_TYPE, "ShaderInclude"))); } ShaderCreateDialog::ShaderCreateDialog() { @@ -547,24 +587,27 @@ ShaderCreateDialog::ShaderCreateDialog() { vb->add_child(status_panel); add_child(vb); - // Language. + // Type. - 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); + type_menu = memnew(OptionButton); + type_menu->set_custom_minimum_size(Size2(250, 0) * EDSCALE); + type_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL); + gc->add_child(memnew(Label(TTR("Type:")))); + gc->add_child(type_menu); for (int i = 0; i < SHADER_TYPE_MAX; i++) { - String language; + String type; bool invalid = false; switch (i) { case SHADER_TYPE_TEXT: - language = "Shader"; - default_language = i; + type = "Shader"; + default_type = i; break; case SHADER_TYPE_VISUAL: - language = "VisualShader"; + type = "VisualShader"; + break; + case SHADER_TYPE_INC: + type = "ShaderInclude"; break; case SHADER_TYPE_MAX: invalid = true; @@ -576,13 +619,13 @@ ShaderCreateDialog::ShaderCreateDialog() { if (invalid) { continue; } - language_menu->add_item(language); + type_menu->add_item(type); } - if (default_language >= 0) { - language_menu->select(default_language); + if (default_type >= 0) { + type_menu->select(default_type); } - current_language = default_language; - language_menu->connect("item_selected", callable_mp(this, &ShaderCreateDialog::_language_changed)); + current_type = default_type; + type_menu->connect("item_selected", callable_mp(this, &ShaderCreateDialog::_type_changed)); // Modes. diff --git a/editor/shader_create_dialog.h b/editor/shader_create_dialog.h index 6737ce4f10..44bd866fbd 100644 --- a/editor/shader_create_dialog.h +++ b/editor/shader_create_dialog.h @@ -47,6 +47,7 @@ class ShaderCreateDialog : public ConfirmationDialog { enum ShaderType { SHADER_TYPE_TEXT, SHADER_TYPE_VISUAL, + SHADER_TYPE_INC, SHADER_TYPE_MAX, }; @@ -56,14 +57,14 @@ class ShaderCreateDialog : public ConfirmationDialog { bool use_templates = false; }; - List<ShaderTypeData> language_data; + List<ShaderTypeData> type_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 *type_menu = nullptr; OptionButton *mode_menu = nullptr; OptionButton *template_menu = nullptr; CheckBox *internal = nullptr; @@ -79,8 +80,8 @@ class ShaderCreateDialog : public ConfirmationDialog { bool built_in_enabled = true; bool load_enabled = false; bool re_check_path = false; - int current_language = -1; - int default_language = -1; + int current_type = -1; + int default_type = -1; int current_mode = 0; int current_template = 0; @@ -89,7 +90,7 @@ class ShaderCreateDialog : public ConfirmationDialog { 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 _type_changed(int p_type = 0); void _built_in_toggled(bool p_enabled); void _template_changed(int p_template = 0); void _mode_changed(int p_mode = 0); |