Path: blob/master/editor/shader/shader_editor_plugin.cpp
20900 views
/**************************************************************************/1/* shader_editor_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "shader_editor_plugin.h"3132#include "editor/docks/editor_dock_manager.h"33#include "editor/docks/filesystem_dock.h"34#include "editor/docks/inspector_dock.h"35#include "editor/editor_node.h"36#include "editor/editor_string_names.h"37#include "editor/editor_undo_redo_manager.h"38#include "editor/gui/window_wrapper.h"39#include "editor/settings/editor_command_palette.h"40#include "editor/shader/shader_create_dialog.h"41#include "editor/shader/text_shader_editor.h"42#include "editor/shader/text_shader_language_plugin.h"43#include "editor/shader/visual_shader_language_plugin.h"44#include "editor/themes/editor_scale.h"45#include "scene/gui/item_list.h"46#include "scene/gui/tab_container.h"47#include "scene/gui/texture_rect.h"4849Ref<Resource> ShaderEditorPlugin::_get_current_shader() {50int index = shader_tabs->get_current_tab();51ERR_FAIL_INDEX_V(index, shader_tabs->get_tab_count(), Ref<Resource>());52if (edited_shaders[index].shader.is_valid()) {53return edited_shaders[index].shader;54} else {55return edited_shaders[index].shader_inc;56}57}5859void ShaderEditorPlugin::_update_shader_list() {60shader_list->clear();61for (EditedShader &edited_shader : edited_shaders) {62Ref<Resource> shader = edited_shader.shader;63if (shader.is_null()) {64shader = edited_shader.shader_inc;65}6667String path = shader->get_path();68String text = path.get_file();69if (text.is_empty()) {70// This appears for newly created built-in shaders before saving the scene.71text = TTR("[unsaved]");72} else if (shader->is_built_in()) {73const String &shader_name = shader->get_name();74if (!shader_name.is_empty()) {75text = vformat("%s (%s)", shader_name, text.get_slice("::", 0));76}77}7879// When shader is deleted in filesystem dock, need this to correctly close shader editor.80edited_shader.path = path;8182bool unsaved = false;83if (edited_shader.shader_editor) {84unsaved = edited_shader.shader_editor->is_unsaved();85}86// TODO: Handle visual shaders too.8788if (unsaved) {89text += "(*)";90}9192String _class = shader->get_class();93if (!shader_list->has_theme_icon(_class, EditorStringName(EditorIcons))) {94_class = "TextFile";95}96Ref<Texture2D> icon = shader_list->get_editor_theme_icon(_class);9798shader_list->add_item(text, icon);99shader_list->set_item_tooltip(-1, path);100edited_shader.name = text;101}102103if (shader_tabs->get_tab_count()) {104shader_list->select(shader_tabs->get_current_tab());105}106107_set_file_specific_items_disabled(edited_shaders.is_empty());108109_update_shader_list_status();110}111112void ShaderEditorPlugin::_update_shader_list_status() {113for (int i = 0; i < shader_list->get_item_count(); i++) {114TextShaderEditor *se = Object::cast_to<TextShaderEditor>(shader_tabs->get_tab_control(i));115if (se) {116if (se->was_compilation_successful()) {117shader_list->set_item_tag_icon(i, Ref<Texture2D>());118} else {119shader_list->set_item_tag_icon(i, shader_list->get_editor_theme_icon(SNAME("Error")));120}121}122}123}124125void ShaderEditorPlugin::_move_shader_tab(int p_from, int p_to) {126if (p_from == p_to) {127return;128}129EditedShader es = edited_shaders[p_from];130edited_shaders.remove_at(p_from);131edited_shaders.insert(p_to, es);132shader_tabs->move_child(shader_tabs->get_tab_control(p_from), p_to);133_update_shader_list();134}135136void ShaderEditorPlugin::edit(Object *p_object) {137if (!p_object) {138return;139}140EditedShader es;141// First, check for ShaderInclude.142ShaderInclude *shader_include = Object::cast_to<ShaderInclude>(p_object);143if (shader_include != nullptr) {144// Check if this shader include is already being edited.145for (uint32_t i = 0; i < edited_shaders.size(); i++) {146if (edited_shaders[i].shader_inc.ptr() == shader_include) {147shader_tabs->set_current_tab(i);148shader_list->select(i);149_switch_to_editor(edited_shaders[i].shader_editor, true);150return;151}152}153es.shader_inc = Ref<ShaderInclude>(shader_include);154for (Ref<EditorShaderLanguagePlugin> shader_lang : EditorShaderLanguagePlugin::get_shader_languages_read_only()) {155if (shader_lang->handles_shader_include(es.shader_inc)) {156es.shader_editor = shader_lang->edit_shader_include(es.shader_inc);157break;158}159}160} else {161// If it's not a ShaderInclude, check for Shader.162Shader *shader = Object::cast_to<Shader>(p_object);163ERR_FAIL_NULL_MSG(shader, "ShaderEditorPlugin: Unable to edit object " + p_object->to_string() + " because it is not a Shader or ShaderInclude.");164// Check if this shader is already being edited.165for (uint32_t i = 0; i < edited_shaders.size(); i++) {166if (edited_shaders[i].shader.ptr() == shader) {167shader_tabs->set_current_tab(i);168shader_list->select(i);169_switch_to_editor(edited_shaders[i].shader_editor, true);170return;171}172}173// If we did not return, the shader needs to be opened in a new shader editor.174es.shader = Ref<Shader>(shader);175for (Ref<EditorShaderLanguagePlugin> shader_lang : EditorShaderLanguagePlugin::get_shader_languages_read_only()) {176if (shader_lang->handles_shader(es.shader)) {177es.shader_editor = shader_lang->edit_shader(es.shader);178break;179}180}181}182183ERR_FAIL_NULL_MSG(es.shader_editor, "ShaderEditorPlugin: Unable to edit shader because no suitable editor was found.");184// TextShaderEditor-specific setup code.185TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(es.shader_editor);186if (text_shader_editor) {187text_shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));188CodeTextEditor *cte = text_shader_editor->get_code_editor();189if (cte) {190cte->set_zoom_factor(text_shader_zoom_factor);191cte->connect("zoomed", callable_mp(this, &ShaderEditorPlugin::_set_text_shader_zoom_factor));192cte->connect(SceneStringName(visibility_changed), callable_mp(this, &ShaderEditorPlugin::_update_shader_editor_zoom_factor).bind(cte));193}194}195196// `set_toggle_list_control` must be called before adding the editor to the scene tree.197es.shader_editor->set_toggle_list_control(shader_list);198shader_tabs->add_child(es.shader_editor);199shader_tabs->set_current_tab(shader_tabs->get_tab_count() - 1);200edited_shaders.push_back(es);201_update_shader_list();202_switch_to_editor(es.shader_editor, !restoring_layout);203}204205bool ShaderEditorPlugin::handles(Object *p_object) const {206return Object::cast_to<Shader>(p_object) != nullptr || Object::cast_to<ShaderInclude>(p_object) != nullptr;207}208209void ShaderEditorPlugin::make_visible(bool p_visible) {210if (p_visible) {211shader_dock->make_visible();212}213}214215ShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) {216for (EditedShader &edited_shader : edited_shaders) {217if (edited_shader.shader == p_for_shader) {218return edited_shader.shader_editor;219}220}221return nullptr;222}223224void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) {225restoring_layout = true;226227if (!bool(EDITOR_GET("editors/shader_editor/behavior/files/restore_shaders_on_load"))) {228return;229}230231if (!p_layout->has_section("ShaderEditor")) {232return;233}234235if (!p_layout->has_section_key("ShaderEditor", "open_shaders") ||236!p_layout->has_section_key("ShaderEditor", "selected_shader")) {237return;238}239240Array shaders = p_layout->get_value("ShaderEditor", "open_shaders");241int selected_shader_idx = 0;242String selected_shader = p_layout->get_value("ShaderEditor", "selected_shader");243for (int i = 0; i < shaders.size(); i++) {244String path = shaders[i];245Ref<Resource> res = ResourceLoader::load(path);246if (res.is_valid()) {247edit(res.ptr());248}249if (selected_shader == path) {250selected_shader_idx = i;251}252}253254if (p_layout->has_section_key("ShaderEditor", "split_offset")) {255files_split->set_split_offset(p_layout->get_value("ShaderEditor", "split_offset"));256}257258_update_shader_list();259_shader_selected(selected_shader_idx, false);260261_set_text_shader_zoom_factor(p_layout->get_value("ShaderEditor", "text_shader_zoom_factor", 1.0f));262263restoring_layout = false;264}265266void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) {267#ifndef DISABLE_DEPRECATED268if (p_layout->has_section_key("ShaderEditor", "window_rect")) {269p_layout->erase_section_key("ShaderEditor", "window_rect");270}271if (p_layout->has_section_key("ShaderEditor", "window_screen")) {272p_layout->erase_section_key("ShaderEditor", "window_screen");273}274if (p_layout->has_section_key("ShaderEditor", "window_screen_rect")) {275p_layout->erase_section_key("ShaderEditor", "window_screen_rect");276}277#endif278279Array shaders;280String selected_shader;281for (int i = 0; i < shader_tabs->get_tab_count(); i++) {282EditedShader edited_shader = edited_shaders[i];283if (edited_shader.shader_editor) {284String shader_path;285if (edited_shader.shader.is_valid()) {286shader_path = edited_shader.shader->get_path();287} else {288DEV_ASSERT(edited_shader.shader_inc.is_valid());289shader_path = edited_shader.shader_inc->get_path();290}291shaders.push_back(shader_path);292293ShaderEditor *shader_editor = Object::cast_to<ShaderEditor>(shader_tabs->get_current_tab_control());294295if (shader_editor && edited_shader.shader_editor == shader_editor) {296selected_shader = shader_path;297}298}299}300p_layout->set_value("ShaderEditor", "open_shaders", shaders);301p_layout->set_value("ShaderEditor", "split_offset", files_split->get_split_offset());302p_layout->set_value("ShaderEditor", "selected_shader", selected_shader);303p_layout->set_value("ShaderEditor", "text_shader_zoom_factor", text_shader_zoom_factor);304}305306String ShaderEditorPlugin::get_unsaved_status(const String &p_for_scene) const {307// TODO: This should also include visual shaders and shader includes, but save_external_data() doesn't seem to save them...308PackedStringArray unsaved_shaders;309for (uint32_t i = 0; i < edited_shaders.size(); i++) {310if (edited_shaders[i].shader_editor) {311if (edited_shaders[i].shader_editor->is_unsaved()) {312if (unsaved_shaders.is_empty()) {313unsaved_shaders.append(TTR("Save changes to the following shaders(s) before quitting?"));314}315unsaved_shaders.append(edited_shaders[i].name.trim_suffix("(*)"));316}317}318}319320if (!p_for_scene.is_empty()) {321PackedStringArray unsaved_built_in_shaders;322323const String scene_file = p_for_scene.get_file();324for (const String &E : unsaved_shaders) {325if (!E.is_resource_file() && E.contains(scene_file)) {326if (unsaved_built_in_shaders.is_empty()) {327unsaved_built_in_shaders.append(TTR("There are unsaved changes in the following built-in shaders(s):"));328}329unsaved_built_in_shaders.append(E);330}331}332333if (!unsaved_built_in_shaders.is_empty()) {334return String("\n").join(unsaved_built_in_shaders);335}336return String();337}338339return String("\n").join(unsaved_shaders);340}341342void ShaderEditorPlugin::save_external_data() {343for (EditedShader &edited_shader : edited_shaders) {344if (edited_shader.shader_editor && edited_shader.shader_editor->is_unsaved()) {345edited_shader.shader_editor->save_external_data();346}347}348_update_shader_list();349}350351void ShaderEditorPlugin::apply_changes() {352for (EditedShader &edited_shader : edited_shaders) {353if (edited_shader.shader_editor) {354edited_shader.shader_editor->apply_shaders();355}356}357}358359void ShaderEditorPlugin::_shader_selected(int p_index, bool p_push_item) {360if (p_index >= (int)edited_shaders.size()) {361return;362}363364if (edited_shaders[p_index].shader_editor) {365_switch_to_editor(edited_shaders[p_index].shader_editor);366edited_shaders[p_index].shader_editor->validate_script();367}368369shader_tabs->set_current_tab(p_index);370shader_list->select(p_index);371372if (p_push_item) {373// Avoid `Shader` being edited when editing `ShaderInclude` due to inspector refreshing.374if (edited_shaders[p_index].shader.is_valid()) {375EditorNode::get_singleton()->push_item_no_inspector(edited_shaders[p_index].shader.ptr());376} else {377EditorNode::get_singleton()->push_item_no_inspector(edited_shaders[p_index].shader_inc.ptr());378}379}380}381382void ShaderEditorPlugin::_shader_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {383if (p_mouse_button_index == MouseButton::MIDDLE) {384_close_shader(p_item);385}386if (p_mouse_button_index == MouseButton::RIGHT) {387_make_script_list_context_menu();388}389}390391void ShaderEditorPlugin::_setup_popup_menu(PopupMenuType p_type, PopupMenu *p_menu) {392if (p_type == FILE) {393p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/new"), FILE_MENU_NEW);394p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/new_include"), FILE_MENU_NEW_INCLUDE);395p_menu->add_separator();396p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/open"), FILE_MENU_OPEN);397p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/open_include"), FILE_MENU_OPEN_INCLUDE);398}399400if (p_type == FILE || p_type == CONTEXT_VALID_ITEM) {401p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save"), FILE_MENU_SAVE);402p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save_as"), FILE_MENU_SAVE_AS);403}404405if (p_type == FILE) {406p_menu->add_separator();407p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/open_in_inspector"), FILE_MENU_INSPECT);408p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/inspect_native_code"), FILE_MENU_INSPECT_NATIVE_SHADER_CODE);409p_menu->add_separator();410p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_MENU_CLOSE);411p_menu->add_separator();412p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/toggle_files_panel"), FILE_MENU_TOGGLE_FILES_PANEL);413} else {414p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_MENU_CLOSE);415p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_all"), FILE_MENU_CLOSE_ALL);416p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_other_tabs"), FILE_MENU_CLOSE_OTHER_TABS);417if (p_type == CONTEXT_VALID_ITEM) {418p_menu->add_separator();419p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/copy_path"), FILE_MENU_COPY_PATH);420p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/show_in_file_system"), FILE_MENU_SHOW_IN_FILE_SYSTEM);421}422}423}424425void ShaderEditorPlugin::_make_script_list_context_menu() {426context_menu->clear();427428int selected = shader_tabs->get_current_tab();429if (selected < 0 || selected >= shader_tabs->get_tab_count()) {430return;431}432433Control *control = shader_tabs->get_tab_control(selected);434bool is_valid_editor_control = Object::cast_to<ShaderEditor>(control) != nullptr;435436_setup_popup_menu(is_valid_editor_control ? CONTEXT_VALID_ITEM : CONTEXT, context_menu);437438context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_ALL), shader_tabs->get_tab_count() <= 0);439context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), shader_tabs->get_tab_count() <= 1);440441context_menu->set_position(files_split->get_screen_position() + files_split->get_local_mouse_position());442context_menu->reset_size();443context_menu->popup();444}445446void ShaderEditorPlugin::_close_shader(int p_index) {447ERR_FAIL_INDEX(p_index, shader_tabs->get_tab_count());448if (file_menu->get_parent() != nullptr) {449file_menu->get_parent()->remove_child(file_menu);450}451ShaderEditor *shader_editor = Object::cast_to<ShaderEditor>(shader_tabs->get_tab_control(p_index));452ERR_FAIL_NULL(shader_editor);453454memdelete(shader_editor);455edited_shaders.remove_at(p_index);456_update_shader_list();457EditorUndoRedoManager::get_singleton()->clear_history(); // To prevent undo on deleted graphs.458459if (shader_tabs->get_tab_count() == 0) {460shader_list->show(); // Make sure the panel is visible, because it can't be toggled without open shaders.461shader_tabs->hide();462files_split->add_child(file_menu);463file_menu->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);464} else {465_switch_to_editor(edited_shaders[shader_tabs->get_current_tab()].shader_editor);466}467}468469void ShaderEditorPlugin::_close_builtin_shaders_from_scene(const String &p_scene) {470for (uint32_t i = 0; i < edited_shaders.size();) {471Ref<Shader> &shader = edited_shaders[i].shader;472if (shader.is_valid()) {473if (shader->is_built_in() && shader->get_path().begins_with(p_scene)) {474_close_shader(i);475continue;476}477}478Ref<ShaderInclude> &include = edited_shaders[i].shader_inc;479if (include.is_valid()) {480if (include->is_built_in() && include->get_path().begins_with(p_scene)) {481_close_shader(i);482continue;483}484}485i++;486}487}488489void ShaderEditorPlugin::_resource_saved(Object *obj) {490// May have been renamed on save.491for (EditedShader &edited_shader : edited_shaders) {492if (edited_shader.shader.ptr() == obj || edited_shader.shader_inc.ptr() == obj) {493_update_shader_list();494return;495}496}497}498499void ShaderEditorPlugin::_menu_item_pressed(int p_index) {500switch (p_index) {501case FILE_MENU_NEW: {502String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir();503shader_create_dialog->config(base_path.path_join("new_shader"), false, false, "Shader");504shader_create_dialog->popup_centered();505} break;506case FILE_MENU_NEW_INCLUDE: {507String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir();508shader_create_dialog->config(base_path.path_join("new_shader"), false, false, "ShaderInclude");509shader_create_dialog->popup_centered();510} break;511case FILE_MENU_OPEN: {512InspectorDock::get_singleton()->open_resource("Shader");513} break;514case FILE_MENU_OPEN_INCLUDE: {515InspectorDock::get_singleton()->open_resource("ShaderInclude");516} break;517case FILE_MENU_SAVE: {518int index = shader_tabs->get_current_tab();519ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());520TextShaderEditor *editor = Object::cast_to<TextShaderEditor>(edited_shaders[index].shader_editor);521if (editor) {522if (editor->get_trim_trailing_whitespace_on_save()) {523editor->trim_trailing_whitespace();524}525526if (editor->get_trim_final_newlines_on_save()) {527editor->trim_final_newlines();528}529}530if (edited_shaders[index].shader.is_valid()) {531EditorNode::get_singleton()->save_resource(edited_shaders[index].shader);532} else {533EditorNode::get_singleton()->save_resource(edited_shaders[index].shader_inc);534}535if (editor) {536editor->tag_saved_version();537}538} break;539case FILE_MENU_SAVE_AS: {540int index = shader_tabs->get_current_tab();541ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());542TextShaderEditor *editor = Object::cast_to<TextShaderEditor>(edited_shaders[index].shader_editor);543if (editor) {544if (editor->get_trim_trailing_whitespace_on_save()) {545editor->trim_trailing_whitespace();546}547548if (editor->get_trim_final_newlines_on_save()) {549editor->trim_final_newlines();550}551}552String path;553if (edited_shaders[index].shader.is_valid()) {554path = edited_shaders[index].shader->get_path();555if (!path.is_resource_file()) {556path = "";557}558EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader, path);559} else {560path = edited_shaders[index].shader_inc->get_path();561if (!path.is_resource_file()) {562path = "";563}564EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader_inc, path);565}566if (editor) {567editor->tag_saved_version();568}569} break;570case FILE_MENU_INSPECT: {571int index = shader_tabs->get_current_tab();572ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());573if (edited_shaders[index].shader.is_valid()) {574EditorNode::get_singleton()->push_item(edited_shaders[index].shader.ptr());575} else {576EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr());577}578} break;579case FILE_MENU_INSPECT_NATIVE_SHADER_CODE: {580int index = shader_tabs->get_current_tab();581if (edited_shaders[index].shader.is_valid()) {582edited_shaders[index].shader->inspect_native_shader_code();583}584} break;585case FILE_MENU_CLOSE: {586_close_shader(shader_tabs->get_current_tab());587} break;588case FILE_MENU_CLOSE_ALL: {589while (shader_tabs->get_tab_count() > 0) {590_close_shader(0);591}592} break;593case FILE_MENU_CLOSE_OTHER_TABS: {594int index = shader_tabs->get_current_tab();595for (int i = 0; i < index; i++) {596_close_shader(0);597}598while (shader_tabs->get_tab_count() > 1) {599_close_shader(1);600}601} break;602case FILE_MENU_SHOW_IN_FILE_SYSTEM: {603Ref<Resource> shader = _get_current_shader();604String path = shader->get_path();605if (!path.is_empty()) {606FileSystemDock::get_singleton()->navigate_to_path(path);607}608} break;609case FILE_MENU_COPY_PATH: {610Ref<Resource> shader = _get_current_shader();611DisplayServer::get_singleton()->clipboard_set(shader->get_path());612} break;613case FILE_MENU_TOGGLE_FILES_PANEL: {614shader_list->set_visible(!shader_list->is_visible());615616int index = shader_tabs->get_current_tab();617if (index != -1) {618ERR_FAIL_INDEX(index, (int)edited_shaders.size());619ShaderEditor *shader_editor = edited_shaders[index].shader_editor;620ERR_FAIL_NULL(shader_editor);621shader_editor->update_toggle_files_button();622}623} break;624}625}626627void ShaderEditorPlugin::_shader_created(Ref<Shader> p_shader) {628EditorNode::get_singleton()->push_item(p_shader.ptr());629}630631void ShaderEditorPlugin::_shader_include_created(Ref<ShaderInclude> p_shader_inc) {632EditorNode::get_singleton()->push_item(p_shader_inc.ptr());633}634635Variant ShaderEditorPlugin::get_drag_data_fw(const Point2 &p_point, Control *p_from) {636if (shader_list->get_item_count() == 0) {637return Variant();638}639640int idx = 0;641if (p_point == Vector2(Math::INF, Math::INF)) {642if (shader_list->is_anything_selected()) {643idx = shader_list->get_selected_items()[0];644}645} else {646idx = shader_list->get_item_at_position(p_point);647}648if (idx < 0) {649return Variant();650}651652HBoxContainer *drag_preview = memnew(HBoxContainer);653String preview_name = shader_list->get_item_text(idx);654Ref<Texture2D> preview_icon = shader_list->get_item_icon(idx);655656if (preview_icon.is_valid()) {657TextureRect *tf = memnew(TextureRect);658tf->set_texture(preview_icon);659tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);660drag_preview->add_child(tf);661}662Label *label = memnew(Label(preview_name));663label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate script names.664drag_preview->add_child(label);665files_split->set_drag_preview(drag_preview);666667Dictionary drag_data;668drag_data["type"] = "shader_list_element";669drag_data["shader_list_element"] = idx;670671Ref<Resource> shader = edited_shaders[idx].shader;672if (shader.is_null()) {673shader = edited_shaders[idx].shader_inc;674}675drag_data["file_path"] = shader->get_path();676677return drag_data;678}679680bool ShaderEditorPlugin::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {681Dictionary d = p_data;682if (!d.has("type")) {683return false;684}685686if (String(d["type"]) == "shader_list_element") {687return true;688}689690if (String(d["type"]) == "files") {691Vector<String> files = d["files"];692693if (files.is_empty()) {694return false;695}696697for (int i = 0; i < files.size(); i++) {698const String &file = files[i];699if (ResourceLoader::exists(file, "Shader")) {700Ref<Shader> shader = ResourceLoader::load(file);701if (shader.is_valid()) {702return true;703}704}705if (ResourceLoader::exists(file, "ShaderInclude")) {706Ref<ShaderInclude> sinclude = ResourceLoader::load(file);707if (sinclude.is_valid()) {708return true;709}710}711}712return false;713}714715return false;716}717718void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {719if (!can_drop_data_fw(p_point, p_data, p_from)) {720return;721}722723Dictionary d = p_data;724if (!d.has("type")) {725return;726}727728if (String(d["type"]) == "shader_list_element") {729int idx = d["shader_list_element"];730int new_idx = 0;731if (p_point == Vector2(Math::INF, Math::INF)) {732if (shader_list->is_anything_selected()) {733new_idx = shader_list->get_selected_items()[0];734}735} else {736new_idx = shader_list->get_item_at_position(p_point);737}738_move_shader_tab(idx, new_idx);739return;740}741742if (String(d["type"]) == "files") {743Vector<String> files = d["files"];744745for (int i = 0; i < files.size(); i++) {746const String &file = files[i];747Ref<Resource> res;748if (ResourceLoader::exists(file, "Shader") || ResourceLoader::exists(file, "ShaderInclude")) {749res = ResourceLoader::load(file);750}751if (res.is_valid()) {752edit(res.ptr());753}754}755}756}757758void ShaderEditorPlugin::_set_text_shader_zoom_factor(float p_zoom_factor) {759if (text_shader_zoom_factor == p_zoom_factor) {760return;761}762763text_shader_zoom_factor = p_zoom_factor;764}765766void ShaderEditorPlugin::_update_shader_editor_zoom_factor(CodeTextEditor *p_shader_editor) const {767if (p_shader_editor && p_shader_editor->is_visible_in_tree() && text_shader_zoom_factor != p_shader_editor->get_zoom_factor()) {768p_shader_editor->set_zoom_factor(text_shader_zoom_factor);769}770}771772void ShaderEditorPlugin::_switch_to_editor(ShaderEditor *p_editor, bool p_focus) {773ERR_FAIL_NULL(p_editor);774if (file_menu->get_parent() != nullptr) {775file_menu->get_parent()->remove_child(file_menu);776}777778shader_tabs->show();779p_editor->use_menu_bar(file_menu);780file_menu->set_v_size_flags(Control::SIZE_EXPAND_FILL);781782if (p_focus) {783TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(p_editor);784if (text_shader_editor) {785text_shader_editor->get_code_editor()->get_text_editor()->grab_focus();786}787}788}789790void ShaderEditorPlugin::_file_removed(const String &p_removed_file) {791for (uint32_t i = 0; i < edited_shaders.size(); i++) {792if (edited_shaders[i].path == p_removed_file) {793_close_shader(i);794break;795}796}797}798799void ShaderEditorPlugin::_res_saved_callback(const Ref<Resource> &p_res) {800if (p_res.is_null()) {801return;802}803const String &path = p_res->get_path();804805for (EditedShader &edited : edited_shaders) {806Ref<Resource> shader_res = edited.shader;807if (shader_res.is_null()) {808shader_res = edited.shader_inc;809}810ERR_FAIL_COND(shader_res.is_null());811812TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(edited.shader_editor);813if (!text_shader_editor || !shader_res->is_built_in()) {814continue;815}816817if (shader_res->get_path().get_slice("::", 0) == path) {818text_shader_editor->tag_saved_version();819_update_shader_list();820}821}822}823824void ShaderEditorPlugin::_set_file_specific_items_disabled(bool p_disabled) {825PopupMenu *file_popup_menu = file_menu->get_popup();826file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_SAVE), p_disabled);827file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_SAVE_AS), p_disabled);828file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_INSPECT), p_disabled);829file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_INSPECT_NATIVE_SHADER_CODE), p_disabled);830file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_CLOSE), p_disabled);831}832833void ShaderEditorPlugin::_notification(int p_what) {834switch (p_what) {835case NOTIFICATION_READY: {836EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED);837EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ShaderEditorPlugin::_close_builtin_shaders_from_scene));838FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ShaderEditorPlugin::_file_removed));839EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ShaderEditorPlugin::_res_saved_callback));840EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));841} break;842}843}844845void ShaderEditorPlugin::shortcut_input(const Ref<InputEvent> &p_event) {846if (p_event.is_null() || !p_event->is_pressed() || p_event->is_echo()) {847return;848}849850if (make_floating_shortcut.is_valid() && make_floating_shortcut->matches_event(p_event)) {851shader_dock->make_floating();852}853}854855ShaderEditorPlugin::ShaderEditorPlugin() {856ED_SHORTCUT("shader_editor/new", TTRC("New Shader..."), KeyModifierMask::CMD_OR_CTRL | Key::N);857ED_SHORTCUT("shader_editor/new_include", TTRC("New Shader Include..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N);858ED_SHORTCUT("shader_editor/open", TTRC("Load Shader File..."), KeyModifierMask::CMD_OR_CTRL | Key::O);859ED_SHORTCUT("shader_editor/open_include", TTRC("Load Shader Include File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O);860ED_SHORTCUT("shader_editor/open_in_inspector", TTRC("Open File in Inspector"));861ED_SHORTCUT("shader_editor/inspect_native_code", TTRC("Inspect Native Shader Code..."));862ED_SHORTCUT("shader_editor/copy_path", TTRC("Copy Shader Path"));863864shader_dock = memnew(EditorDock);865shader_dock->set_name(TTRC("Shader Editor"));866shader_dock->set_icon_name("ShaderDock");867shader_dock->set_dock_shortcut(ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTRC("Toggle Shader Editor Dock"), KeyModifierMask::ALT | Key::S));868shader_dock->set_default_slot(EditorDock::DOCK_SLOT_BOTTOM);869shader_dock->set_available_layouts(EditorDock::DOCK_LAYOUT_HORIZONTAL | EditorDock::DOCK_LAYOUT_FLOATING);870shader_dock->set_custom_minimum_size(Size2(460, 300) * EDSCALE);871EditorDockManager::get_singleton()->add_dock(shader_dock);872873set_process_shortcut_input(true);874875make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTRC("Make Floating"));876877files_split = memnew(HSplitContainer);878files_split->set_split_offset(200 * EDSCALE);879files_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);880shader_dock->add_child(files_split);881882context_menu = memnew(PopupMenu);883context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));884add_child(context_menu);885886shader_list = memnew(ItemList);887shader_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);888shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);889shader_list->set_theme_type_variation("ItemListSecondary");890shader_list->set_custom_minimum_size(Size2(100, 60) * EDSCALE);891shader_list->connect(SceneStringName(item_selected), callable_mp(this, &ShaderEditorPlugin::_shader_selected).bind(true));892shader_list->connect("item_clicked", callable_mp(this, &ShaderEditorPlugin::_shader_list_clicked));893shader_list->set_allow_rmb_select(true);894SET_DRAG_FORWARDING_GCD(shader_list, ShaderEditorPlugin);895files_split->add_child(shader_list);896897shader_tabs = memnew(TabContainer);898shader_tabs->set_tabs_visible(false);899shader_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);900shader_tabs->set_v_size_flags(Control::SIZE_EXPAND_FILL);901Ref<StyleBoxEmpty> empty;902empty.instantiate();903shader_tabs->add_theme_style_override(SceneStringName(panel), empty);904shader_tabs->hide();905files_split->add_child(shader_tabs);906907file_menu = memnew(MenuButton);908file_menu->set_flat(false);909file_menu->set_theme_type_variation("FlatMenuButton");910file_menu->set_text(TTRC("File"));911file_menu->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);912file_menu->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);913file_menu->set_switch_on_hover(true);914file_menu->set_shortcut_context(files_split);915file_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));916_setup_popup_menu(FILE, file_menu->get_popup());917_set_file_specific_items_disabled(true);918files_split->add_child(file_menu);919920shader_create_dialog = memnew(ShaderCreateDialog);921shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created));922shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created));923shader_dock->add_child(shader_create_dialog);924925Ref<TextShaderLanguagePlugin> text_shader_lang;926text_shader_lang.instantiate();927EditorShaderLanguagePlugin::register_shader_language(text_shader_lang);928929Ref<VisualShaderLanguagePlugin> visual_shader_lang;930visual_shader_lang.instantiate();931EditorShaderLanguagePlugin::register_shader_language(visual_shader_lang);932}933934ShaderEditorPlugin::~ShaderEditorPlugin() {935EditorShaderLanguagePlugin::clear_registered_shader_languages();936memdelete(file_menu);937}938939940