1/**************************************************************************/
2/* shader_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "shader_editor_plugin.h"
32
33#include "editor/editor_command_palette.h"
34#include "editor/editor_node.h"
35#include "editor/editor_scale.h"
36#include "editor/editor_string_names.h"
37#include "editor/editor_undo_redo_manager.h"
38#include "editor/filesystem_dock.h"
39#include "editor/inspector_dock.h"
40#include "editor/plugins/text_shader_editor.h"
41#include "editor/plugins/visual_shader_editor_plugin.h"
42#include "editor/shader_create_dialog.h"
43#include "editor/window_wrapper.h"
44#include "scene/gui/item_list.h"
45#include "scene/gui/texture_rect.h"
46
47void ShaderEditorPlugin::_update_shader_list() {
48 shader_list->clear();
49 for (EditedShader &edited_shader : edited_shaders) {
50 Ref<Resource> shader = edited_shader.shader;
51 if (shader.is_null()) {
52 shader = edited_shader.shader_inc;
53 }
54
55 String path = shader->get_path();
56 String text = path.get_file();
57 if (text.is_empty()) {
58 // This appears for newly created built-in shaders before saving the scene.
59 text = TTR("[unsaved]");
60 } else if (shader->is_built_in()) {
61 const String &shader_name = shader->get_name();
62 if (!shader_name.is_empty()) {
63 text = vformat("%s (%s)", shader_name, text.get_slice("::", 0));
64 }
65 }
66
67 bool unsaved = false;
68 if (edited_shader.shader_editor) {
69 unsaved = edited_shader.shader_editor->is_unsaved();
70 }
71 // TODO: Handle visual shaders too.
72
73 if (unsaved) {
74 text += "(*)";
75 }
76
77 String _class = shader->get_class();
78 if (!shader_list->has_theme_icon(_class, EditorStringName(EditorIcons))) {
79 _class = "TextFile";
80 }
81 Ref<Texture2D> icon = shader_list->get_editor_theme_icon(_class);
82
83 shader_list->add_item(text, icon);
84 shader_list->set_item_tooltip(shader_list->get_item_count() - 1, path);
85 }
86
87 if (shader_tabs->get_tab_count()) {
88 shader_list->select(shader_tabs->get_current_tab());
89 }
90
91 for (int i = FILE_SAVE; i < FILE_MAX; i++) {
92 file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.is_empty());
93 }
94
95 _update_shader_list_status();
96}
97
98void ShaderEditorPlugin::_update_shader_list_status() {
99 for (int i = 0; i < shader_list->get_item_count(); i++) {
100 TextShaderEditor *se = Object::cast_to<TextShaderEditor>(shader_tabs->get_tab_control(i));
101 if (se) {
102 if (se->was_compilation_successful()) {
103 shader_list->set_item_tag_icon(i, Ref<Texture2D>());
104 } else {
105 shader_list->set_item_tag_icon(i, shader_list->get_editor_theme_icon(SNAME("Error")));
106 }
107 }
108 }
109}
110
111void ShaderEditorPlugin::_move_shader_tab(int p_from, int p_to) {
112 if (p_from == p_to) {
113 return;
114 }
115 EditedShader es = edited_shaders[p_from];
116 edited_shaders.remove_at(p_from);
117 edited_shaders.insert(p_to, es);
118 shader_tabs->move_child(shader_tabs->get_tab_control(p_from), p_to);
119 _update_shader_list();
120}
121
122void ShaderEditorPlugin::edit(Object *p_object) {
123 if (!p_object) {
124 return;
125 }
126
127 EditedShader es;
128
129 ShaderInclude *si = Object::cast_to<ShaderInclude>(p_object);
130 if (si != nullptr) {
131 for (uint32_t i = 0; i < edited_shaders.size(); i++) {
132 if (edited_shaders[i].shader_inc.ptr() == si) {
133 shader_tabs->set_current_tab(i);
134 shader_list->select(i);
135 return;
136 }
137 }
138 es.shader_inc = Ref<ShaderInclude>(si);
139 es.shader_editor = memnew(TextShaderEditor);
140 es.shader_editor->edit(si);
141 shader_tabs->add_child(es.shader_editor);
142 es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));
143 } else {
144 Shader *s = Object::cast_to<Shader>(p_object);
145 for (uint32_t i = 0; i < edited_shaders.size(); i++) {
146 if (edited_shaders[i].shader.ptr() == s) {
147 shader_tabs->set_current_tab(i);
148 shader_list->select(i);
149 return;
150 }
151 }
152 es.shader = Ref<Shader>(s);
153 Ref<VisualShader> vs = es.shader;
154 if (vs.is_valid()) {
155 es.visual_shader_editor = memnew(VisualShaderEditor);
156 shader_tabs->add_child(es.visual_shader_editor);
157 es.visual_shader_editor->edit(vs.ptr());
158 } else {
159 es.shader_editor = memnew(TextShaderEditor);
160 shader_tabs->add_child(es.shader_editor);
161 es.shader_editor->edit(s);
162 es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));
163 }
164 }
165
166 shader_tabs->set_current_tab(shader_tabs->get_tab_count() - 1);
167 edited_shaders.push_back(es);
168 _update_shader_list();
169}
170
171bool ShaderEditorPlugin::handles(Object *p_object) const {
172 return Object::cast_to<Shader>(p_object) != nullptr || Object::cast_to<ShaderInclude>(p_object) != nullptr;
173}
174
175void ShaderEditorPlugin::make_visible(bool p_visible) {
176 if (p_visible) {
177 EditorNode::get_singleton()->make_bottom_panel_item_visible(window_wrapper);
178 }
179}
180
181void ShaderEditorPlugin::selected_notify() {
182}
183
184TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) {
185 for (EditedShader &edited_shader : edited_shaders) {
186 if (edited_shader.shader == p_for_shader) {
187 return edited_shader.shader_editor;
188 }
189 }
190 return nullptr;
191}
192
193VisualShaderEditor *ShaderEditorPlugin::get_visual_shader_editor(const Ref<Shader> &p_for_shader) {
194 for (EditedShader &edited_shader : edited_shaders) {
195 if (edited_shader.shader == p_for_shader) {
196 return edited_shader.visual_shader_editor;
197 }
198 }
199 return nullptr;
200}
201
202void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
203 if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ShaderEditor", "window_rect")) {
204 window_wrapper->restore_window_from_saved_position(
205 p_layout->get_value("ShaderEditor", "window_rect", Rect2i()),
206 p_layout->get_value("ShaderEditor", "window_screen", -1),
207 p_layout->get_value("ShaderEditor", "window_screen_rect", Rect2i()));
208 } else {
209 window_wrapper->set_window_enabled(false);
210 }
211
212 if (!bool(EDITOR_GET("editors/shader_editor/behavior/files/restore_shaders_on_load"))) {
213 return;
214 }
215 if (!p_layout->has_section("ShaderEditor")) {
216 return;
217 }
218 if (!p_layout->has_section_key("ShaderEditor", "open_shaders") ||
219 !p_layout->has_section_key("ShaderEditor", "selected_shader")) {
220 return;
221 }
222
223 Array shaders = p_layout->get_value("ShaderEditor", "open_shaders");
224 int selected_shader_idx = 0;
225 String selected_shader = p_layout->get_value("ShaderEditor", "selected_shader");
226 for (int i = 0; i < shaders.size(); i++) {
227 String path = shaders[i];
228 Ref<Resource> res = ResourceLoader::load(path);
229 if (res.is_valid()) {
230 edit(res.ptr());
231 }
232 if (selected_shader == path) {
233 selected_shader_idx = i;
234 }
235 }
236
237 if (p_layout->has_section_key("ShaderEditor", "split_offset")) {
238 main_split->set_split_offset(p_layout->get_value("ShaderEditor", "split_offset"));
239 }
240
241 _update_shader_list();
242 _shader_selected(selected_shader_idx);
243}
244
245void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
246 if (window_wrapper->get_window_enabled()) {
247 p_layout->set_value("ShaderEditor", "window_rect", window_wrapper->get_window_rect());
248 int screen = window_wrapper->get_window_screen();
249 p_layout->set_value("ShaderEditor", "window_screen", screen);
250 p_layout->set_value("ShaderEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen));
251
252 } else {
253 if (p_layout->has_section_key("ShaderEditor", "window_rect")) {
254 p_layout->erase_section_key("ShaderEditor", "window_rect");
255 }
256 if (p_layout->has_section_key("ShaderEditor", "window_screen")) {
257 p_layout->erase_section_key("ShaderEditor", "window_screen");
258 }
259 if (p_layout->has_section_key("ShaderEditor", "window_screen_rect")) {
260 p_layout->erase_section_key("ShaderEditor", "window_screen_rect");
261 }
262 }
263
264 Array shaders;
265 String selected_shader;
266 for (int i = 0; i < shader_tabs->get_tab_count(); i++) {
267 EditedShader edited_shader = edited_shaders[i];
268 if (edited_shader.shader_editor || edited_shader.visual_shader_editor) {
269 String shader_path;
270 if (edited_shader.shader.is_valid()) {
271 shader_path = edited_shader.shader->get_path();
272 } else {
273 DEV_ASSERT(edited_shader.shader_inc.is_valid());
274 shader_path = edited_shader.shader_inc->get_path();
275 }
276 shaders.push_back(shader_path);
277
278 TextShaderEditor *shader_editor = Object::cast_to<TextShaderEditor>(shader_tabs->get_current_tab_control());
279 VisualShaderEditor *visual_shader_editor = Object::cast_to<VisualShaderEditor>(shader_tabs->get_current_tab_control());
280
281 if ((shader_editor && edited_shader.shader_editor == shader_editor) || (visual_shader_editor && edited_shader.visual_shader_editor == visual_shader_editor)) {
282 selected_shader = shader_path;
283 }
284 }
285 }
286 p_layout->set_value("ShaderEditor", "open_shaders", shaders);
287 p_layout->set_value("ShaderEditor", "split_offset", main_split->get_split_offset());
288 p_layout->set_value("ShaderEditor", "selected_shader", selected_shader);
289}
290
291String ShaderEditorPlugin::get_unsaved_status(const String &p_for_scene) const {
292 if (!p_for_scene.is_empty()) {
293 // TODO: handle built-in shaders.
294 return String();
295 }
296
297 // TODO: This should also include visual shaders and shader includes, but save_external_data() doesn't seem to save them...
298 PackedStringArray unsaved_shaders;
299 for (uint32_t i = 0; i < edited_shaders.size(); i++) {
300 if (edited_shaders[i].shader_editor) {
301 if (edited_shaders[i].shader_editor->is_unsaved()) {
302 if (unsaved_shaders.is_empty()) {
303 unsaved_shaders.append(TTR("Save changes to the following shaders(s) before quitting?"));
304 }
305 unsaved_shaders.append(edited_shaders[i].shader_editor->get_name());
306 }
307 }
308 }
309 return String("\n").join(unsaved_shaders);
310}
311
312void ShaderEditorPlugin::save_external_data() {
313 for (EditedShader &edited_shader : edited_shaders) {
314 if (edited_shader.shader_editor) {
315 edited_shader.shader_editor->save_external_data();
316 }
317 }
318 _update_shader_list();
319}
320
321void ShaderEditorPlugin::apply_changes() {
322 for (EditedShader &edited_shader : edited_shaders) {
323 if (edited_shader.shader_editor) {
324 edited_shader.shader_editor->apply_shaders();
325 }
326 }
327}
328
329void ShaderEditorPlugin::_shader_selected(int p_index) {
330 if (p_index >= (int)edited_shaders.size()) {
331 return;
332 }
333
334 if (edited_shaders[p_index].shader_editor) {
335 edited_shaders[p_index].shader_editor->validate_script();
336 }
337 shader_tabs->set_current_tab(p_index);
338 shader_list->select(p_index);
339}
340
341void ShaderEditorPlugin::_shader_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {
342 if (p_mouse_button_index == MouseButton::MIDDLE) {
343 _close_shader(p_item);
344 }
345}
346
347void ShaderEditorPlugin::_close_shader(int p_index) {
348 ERR_FAIL_INDEX(p_index, shader_tabs->get_tab_count());
349 Control *c = shader_tabs->get_tab_control(p_index);
350 memdelete(c);
351 edited_shaders.remove_at(p_index);
352 _update_shader_list();
353 EditorUndoRedoManager::get_singleton()->clear_history(); // To prevent undo on deleted graphs.
354}
355
356void ShaderEditorPlugin::_close_builtin_shaders_from_scene(const String &p_scene) {
357 for (uint32_t i = 0; i < edited_shaders.size();) {
358 Ref<Shader> &shader = edited_shaders[i].shader;
359 if (shader.is_valid()) {
360 if (shader->is_built_in() && shader->get_path().begins_with(p_scene)) {
361 _close_shader(i);
362 continue;
363 }
364 }
365 Ref<ShaderInclude> &include = edited_shaders[i].shader_inc;
366 if (include.is_valid()) {
367 if (include->is_built_in() && include->get_path().begins_with(p_scene)) {
368 _close_shader(i);
369 continue;
370 }
371 }
372 i++;
373 }
374}
375
376void ShaderEditorPlugin::_resource_saved(Object *obj) {
377 // May have been renamed on save.
378 for (EditedShader &edited_shader : edited_shaders) {
379 if (edited_shader.shader.ptr() == obj) {
380 _update_shader_list();
381 return;
382 }
383 }
384}
385
386void ShaderEditorPlugin::_menu_item_pressed(int p_index) {
387 switch (p_index) {
388 case FILE_NEW: {
389 String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir();
390 shader_create_dialog->config(base_path.path_join("new_shader"), false, false, 0);
391 shader_create_dialog->popup_centered();
392 } break;
393 case FILE_NEW_INCLUDE: {
394 String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir();
395 shader_create_dialog->config(base_path.path_join("new_shader"), false, false, 2);
396 shader_create_dialog->popup_centered();
397 } break;
398 case FILE_OPEN: {
399 InspectorDock::get_singleton()->open_resource("Shader");
400 } break;
401 case FILE_OPEN_INCLUDE: {
402 InspectorDock::get_singleton()->open_resource("ShaderInclude");
403 } break;
404 case FILE_SAVE: {
405 int index = shader_tabs->get_current_tab();
406 ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());
407 TextShaderEditor *editor = edited_shaders[index].shader_editor;
408 if (editor && editor->get_trim_trailing_whitespace_on_save()) {
409 editor->trim_trailing_whitespace();
410 }
411 if (edited_shaders[index].shader.is_valid()) {
412 EditorNode::get_singleton()->save_resource(edited_shaders[index].shader);
413 } else {
414 EditorNode::get_singleton()->save_resource(edited_shaders[index].shader_inc);
415 }
416 if (editor) {
417 editor->tag_saved_version();
418 }
419 } break;
420 case FILE_SAVE_AS: {
421 int index = shader_tabs->get_current_tab();
422 ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());
423 TextShaderEditor *editor = edited_shaders[index].shader_editor;
424 if (editor && editor->get_trim_trailing_whitespace_on_save()) {
425 editor->trim_trailing_whitespace();
426 }
427 String path;
428 if (edited_shaders[index].shader.is_valid()) {
429 path = edited_shaders[index].shader->get_path();
430 if (!path.is_resource_file()) {
431 path = "";
432 }
433 EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader, path);
434 } else {
435 path = edited_shaders[index].shader_inc->get_path();
436 if (!path.is_resource_file()) {
437 path = "";
438 }
439 EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader_inc, path);
440 }
441 if (editor) {
442 editor->tag_saved_version();
443 }
444 } break;
445 case FILE_INSPECT: {
446 int index = shader_tabs->get_current_tab();
447 ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());
448 if (edited_shaders[index].shader.is_valid()) {
449 EditorNode::get_singleton()->push_item(edited_shaders[index].shader.ptr());
450 } else {
451 EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr());
452 }
453 } break;
454 case FILE_CLOSE: {
455 _close_shader(shader_tabs->get_current_tab());
456 } break;
457 }
458}
459
460void ShaderEditorPlugin::_shader_created(Ref<Shader> p_shader) {
461 EditorNode::get_singleton()->push_item(p_shader.ptr());
462}
463
464void ShaderEditorPlugin::_shader_include_created(Ref<ShaderInclude> p_shader_inc) {
465 EditorNode::get_singleton()->push_item(p_shader_inc.ptr());
466}
467
468Variant ShaderEditorPlugin::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
469 if (shader_list->get_item_count() == 0) {
470 return Variant();
471 }
472
473 int idx = shader_list->get_item_at_position(p_point);
474 if (idx < 0) {
475 return Variant();
476 }
477
478 HBoxContainer *drag_preview = memnew(HBoxContainer);
479 String preview_name = shader_list->get_item_text(idx);
480 Ref<Texture2D> preview_icon = shader_list->get_item_icon(idx);
481
482 if (!preview_icon.is_null()) {
483 TextureRect *tf = memnew(TextureRect);
484 tf->set_texture(preview_icon);
485 tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
486 drag_preview->add_child(tf);
487 }
488 Label *label = memnew(Label(preview_name));
489 drag_preview->add_child(label);
490 main_split->set_drag_preview(drag_preview);
491
492 Dictionary drag_data;
493 drag_data["type"] = "shader_list_element";
494 drag_data["shader_list_element"] = idx;
495
496 return drag_data;
497}
498
499bool ShaderEditorPlugin::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
500 Dictionary d = p_data;
501 if (!d.has("type")) {
502 return false;
503 }
504
505 if (String(d["type"]) == "shader_list_element") {
506 return true;
507 }
508
509 if (String(d["type"]) == "files") {
510 Vector<String> files = d["files"];
511
512 if (files.size() == 0) {
513 return false;
514 }
515
516 for (int i = 0; i < files.size(); i++) {
517 String file = files[i];
518 if (ResourceLoader::exists(file, "Shader")) {
519 Ref<Shader> shader = ResourceLoader::load(file);
520 if (shader.is_valid()) {
521 return true;
522 }
523 }
524 if (ResourceLoader::exists(file, "ShaderInclude")) {
525 Ref<ShaderInclude> sinclude = ResourceLoader::load(file);
526 if (sinclude.is_valid()) {
527 return true;
528 }
529 }
530 }
531 return false;
532 }
533
534 return false;
535}
536
537void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
538 if (!can_drop_data_fw(p_point, p_data, p_from)) {
539 return;
540 }
541
542 Dictionary d = p_data;
543 if (!d.has("type")) {
544 return;
545 }
546
547 if (String(d["type"]) == "shader_list_element") {
548 int idx = d["shader_list_element"];
549 int new_idx = shader_list->get_item_at_position(p_point);
550 _move_shader_tab(idx, new_idx);
551 return;
552 }
553
554 if (String(d["type"]) == "files") {
555 Vector<String> files = d["files"];
556
557 for (int i = 0; i < files.size(); i++) {
558 String file = files[i];
559 Ref<Resource> res;
560 if (ResourceLoader::exists(file, "Shader") || ResourceLoader::exists(file, "ShaderInclude")) {
561 res = ResourceLoader::load(file);
562 }
563 if (res.is_valid()) {
564 edit(res.ptr());
565 }
566 }
567 }
568}
569
570void ShaderEditorPlugin::_window_changed(bool p_visible) {
571 make_floating->set_visible(!p_visible);
572}
573
574void ShaderEditorPlugin::_notification(int p_what) {
575 switch (p_what) {
576 case NOTIFICATION_READY: {
577 EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ShaderEditorPlugin::_close_builtin_shaders_from_scene));
578 } break;
579 }
580}
581
582ShaderEditorPlugin::ShaderEditorPlugin() {
583 window_wrapper = memnew(WindowWrapper);
584 window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Shader Editor")));
585 window_wrapper->set_margins_enabled(true);
586
587 main_split = memnew(HSplitContainer);
588 Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTR("Make Floating"));
589 window_wrapper->set_wrapped_control(main_split, make_floating_shortcut);
590
591 VBoxContainer *vb = memnew(VBoxContainer);
592
593 HBoxContainer *menu_hb = memnew(HBoxContainer);
594 vb->add_child(menu_hb);
595 file_menu = memnew(MenuButton);
596 file_menu->set_text(TTR("File"));
597 file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW);
598 file_menu->get_popup()->add_item(TTR("New Shader Include"), FILE_NEW_INCLUDE);
599 file_menu->get_popup()->add_separator();
600 file_menu->get_popup()->add_item(TTR("Load Shader File"), FILE_OPEN);
601 file_menu->get_popup()->add_item(TTR("Load Shader Include File"), FILE_OPEN_INCLUDE);
602 file_menu->get_popup()->add_item(TTR("Save File"), FILE_SAVE);
603 file_menu->get_popup()->add_item(TTR("Save File As"), FILE_SAVE_AS);
604 file_menu->get_popup()->add_separator();
605 file_menu->get_popup()->add_item(TTR("Open File in Inspector"), FILE_INSPECT);
606 file_menu->get_popup()->add_separator();
607 file_menu->get_popup()->add_item(TTR("Close File"), FILE_CLOSE);
608 file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
609 menu_hb->add_child(file_menu);
610
611 for (int i = FILE_SAVE; i < FILE_MAX; i++) {
612 file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true);
613 }
614
615 if (window_wrapper->is_window_available()) {
616 Control *padding = memnew(Control);
617 padding->set_h_size_flags(Control::SIZE_EXPAND_FILL);
618 menu_hb->add_child(padding);
619
620 make_floating = memnew(ScreenSelect);
621 make_floating->set_flat(true);
622 make_floating->set_tooltip_text(TTR("Make the shader editor floating."));
623 make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
624
625 menu_hb->add_child(make_floating);
626 window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed));
627 }
628
629 shader_list = memnew(ItemList);
630 shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
631 vb->add_child(shader_list);
632 shader_list->connect("item_selected", callable_mp(this, &ShaderEditorPlugin::_shader_selected));
633 shader_list->connect("item_clicked", callable_mp(this, &ShaderEditorPlugin::_shader_list_clicked));
634 SET_DRAG_FORWARDING_GCD(shader_list, ShaderEditorPlugin);
635
636 main_split->add_child(vb);
637 vb->set_custom_minimum_size(Size2(200, 300) * EDSCALE);
638
639 shader_tabs = memnew(TabContainer);
640 shader_tabs->set_tabs_visible(false);
641 shader_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
642 main_split->add_child(shader_tabs);
643 Ref<StyleBoxEmpty> empty;
644 empty.instantiate();
645 shader_tabs->add_theme_style_override("panel", empty);
646
647 button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader Editor"), window_wrapper);
648
649 // Defer connect because Editor class is not in the binding system yet.
650 EditorNode::get_singleton()->call_deferred("connect", "resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED);
651
652 shader_create_dialog = memnew(ShaderCreateDialog);
653 vb->add_child(shader_create_dialog);
654 shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created));
655 shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created));
656}
657
658ShaderEditorPlugin::~ShaderEditorPlugin() {
659}
660