Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/shader/shader_editor_plugin.cpp
20900 views
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/docks/editor_dock_manager.h"
34
#include "editor/docks/filesystem_dock.h"
35
#include "editor/docks/inspector_dock.h"
36
#include "editor/editor_node.h"
37
#include "editor/editor_string_names.h"
38
#include "editor/editor_undo_redo_manager.h"
39
#include "editor/gui/window_wrapper.h"
40
#include "editor/settings/editor_command_palette.h"
41
#include "editor/shader/shader_create_dialog.h"
42
#include "editor/shader/text_shader_editor.h"
43
#include "editor/shader/text_shader_language_plugin.h"
44
#include "editor/shader/visual_shader_language_plugin.h"
45
#include "editor/themes/editor_scale.h"
46
#include "scene/gui/item_list.h"
47
#include "scene/gui/tab_container.h"
48
#include "scene/gui/texture_rect.h"
49
50
Ref<Resource> ShaderEditorPlugin::_get_current_shader() {
51
int index = shader_tabs->get_current_tab();
52
ERR_FAIL_INDEX_V(index, shader_tabs->get_tab_count(), Ref<Resource>());
53
if (edited_shaders[index].shader.is_valid()) {
54
return edited_shaders[index].shader;
55
} else {
56
return edited_shaders[index].shader_inc;
57
}
58
}
59
60
void ShaderEditorPlugin::_update_shader_list() {
61
shader_list->clear();
62
for (EditedShader &edited_shader : edited_shaders) {
63
Ref<Resource> shader = edited_shader.shader;
64
if (shader.is_null()) {
65
shader = edited_shader.shader_inc;
66
}
67
68
String path = shader->get_path();
69
String text = path.get_file();
70
if (text.is_empty()) {
71
// This appears for newly created built-in shaders before saving the scene.
72
text = TTR("[unsaved]");
73
} else if (shader->is_built_in()) {
74
const String &shader_name = shader->get_name();
75
if (!shader_name.is_empty()) {
76
text = vformat("%s (%s)", shader_name, text.get_slice("::", 0));
77
}
78
}
79
80
// When shader is deleted in filesystem dock, need this to correctly close shader editor.
81
edited_shader.path = path;
82
83
bool unsaved = false;
84
if (edited_shader.shader_editor) {
85
unsaved = edited_shader.shader_editor->is_unsaved();
86
}
87
// TODO: Handle visual shaders too.
88
89
if (unsaved) {
90
text += "(*)";
91
}
92
93
String _class = shader->get_class();
94
if (!shader_list->has_theme_icon(_class, EditorStringName(EditorIcons))) {
95
_class = "TextFile";
96
}
97
Ref<Texture2D> icon = shader_list->get_editor_theme_icon(_class);
98
99
shader_list->add_item(text, icon);
100
shader_list->set_item_tooltip(-1, path);
101
edited_shader.name = text;
102
}
103
104
if (shader_tabs->get_tab_count()) {
105
shader_list->select(shader_tabs->get_current_tab());
106
}
107
108
_set_file_specific_items_disabled(edited_shaders.is_empty());
109
110
_update_shader_list_status();
111
}
112
113
void ShaderEditorPlugin::_update_shader_list_status() {
114
for (int i = 0; i < shader_list->get_item_count(); i++) {
115
TextShaderEditor *se = Object::cast_to<TextShaderEditor>(shader_tabs->get_tab_control(i));
116
if (se) {
117
if (se->was_compilation_successful()) {
118
shader_list->set_item_tag_icon(i, Ref<Texture2D>());
119
} else {
120
shader_list->set_item_tag_icon(i, shader_list->get_editor_theme_icon(SNAME("Error")));
121
}
122
}
123
}
124
}
125
126
void ShaderEditorPlugin::_move_shader_tab(int p_from, int p_to) {
127
if (p_from == p_to) {
128
return;
129
}
130
EditedShader es = edited_shaders[p_from];
131
edited_shaders.remove_at(p_from);
132
edited_shaders.insert(p_to, es);
133
shader_tabs->move_child(shader_tabs->get_tab_control(p_from), p_to);
134
_update_shader_list();
135
}
136
137
void ShaderEditorPlugin::edit(Object *p_object) {
138
if (!p_object) {
139
return;
140
}
141
EditedShader es;
142
// First, check for ShaderInclude.
143
ShaderInclude *shader_include = Object::cast_to<ShaderInclude>(p_object);
144
if (shader_include != nullptr) {
145
// Check if this shader include is already being edited.
146
for (uint32_t i = 0; i < edited_shaders.size(); i++) {
147
if (edited_shaders[i].shader_inc.ptr() == shader_include) {
148
shader_tabs->set_current_tab(i);
149
shader_list->select(i);
150
_switch_to_editor(edited_shaders[i].shader_editor, true);
151
return;
152
}
153
}
154
es.shader_inc = Ref<ShaderInclude>(shader_include);
155
for (Ref<EditorShaderLanguagePlugin> shader_lang : EditorShaderLanguagePlugin::get_shader_languages_read_only()) {
156
if (shader_lang->handles_shader_include(es.shader_inc)) {
157
es.shader_editor = shader_lang->edit_shader_include(es.shader_inc);
158
break;
159
}
160
}
161
} else {
162
// If it's not a ShaderInclude, check for Shader.
163
Shader *shader = Object::cast_to<Shader>(p_object);
164
ERR_FAIL_NULL_MSG(shader, "ShaderEditorPlugin: Unable to edit object " + p_object->to_string() + " because it is not a Shader or ShaderInclude.");
165
// Check if this shader is already being edited.
166
for (uint32_t i = 0; i < edited_shaders.size(); i++) {
167
if (edited_shaders[i].shader.ptr() == shader) {
168
shader_tabs->set_current_tab(i);
169
shader_list->select(i);
170
_switch_to_editor(edited_shaders[i].shader_editor, true);
171
return;
172
}
173
}
174
// If we did not return, the shader needs to be opened in a new shader editor.
175
es.shader = Ref<Shader>(shader);
176
for (Ref<EditorShaderLanguagePlugin> shader_lang : EditorShaderLanguagePlugin::get_shader_languages_read_only()) {
177
if (shader_lang->handles_shader(es.shader)) {
178
es.shader_editor = shader_lang->edit_shader(es.shader);
179
break;
180
}
181
}
182
}
183
184
ERR_FAIL_NULL_MSG(es.shader_editor, "ShaderEditorPlugin: Unable to edit shader because no suitable editor was found.");
185
// TextShaderEditor-specific setup code.
186
TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(es.shader_editor);
187
if (text_shader_editor) {
188
text_shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));
189
CodeTextEditor *cte = text_shader_editor->get_code_editor();
190
if (cte) {
191
cte->set_zoom_factor(text_shader_zoom_factor);
192
cte->connect("zoomed", callable_mp(this, &ShaderEditorPlugin::_set_text_shader_zoom_factor));
193
cte->connect(SceneStringName(visibility_changed), callable_mp(this, &ShaderEditorPlugin::_update_shader_editor_zoom_factor).bind(cte));
194
}
195
}
196
197
// `set_toggle_list_control` must be called before adding the editor to the scene tree.
198
es.shader_editor->set_toggle_list_control(shader_list);
199
shader_tabs->add_child(es.shader_editor);
200
shader_tabs->set_current_tab(shader_tabs->get_tab_count() - 1);
201
edited_shaders.push_back(es);
202
_update_shader_list();
203
_switch_to_editor(es.shader_editor, !restoring_layout);
204
}
205
206
bool ShaderEditorPlugin::handles(Object *p_object) const {
207
return Object::cast_to<Shader>(p_object) != nullptr || Object::cast_to<ShaderInclude>(p_object) != nullptr;
208
}
209
210
void ShaderEditorPlugin::make_visible(bool p_visible) {
211
if (p_visible) {
212
shader_dock->make_visible();
213
}
214
}
215
216
ShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) {
217
for (EditedShader &edited_shader : edited_shaders) {
218
if (edited_shader.shader == p_for_shader) {
219
return edited_shader.shader_editor;
220
}
221
}
222
return nullptr;
223
}
224
225
void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
226
restoring_layout = true;
227
228
if (!bool(EDITOR_GET("editors/shader_editor/behavior/files/restore_shaders_on_load"))) {
229
return;
230
}
231
232
if (!p_layout->has_section("ShaderEditor")) {
233
return;
234
}
235
236
if (!p_layout->has_section_key("ShaderEditor", "open_shaders") ||
237
!p_layout->has_section_key("ShaderEditor", "selected_shader")) {
238
return;
239
}
240
241
Array shaders = p_layout->get_value("ShaderEditor", "open_shaders");
242
int selected_shader_idx = 0;
243
String selected_shader = p_layout->get_value("ShaderEditor", "selected_shader");
244
for (int i = 0; i < shaders.size(); i++) {
245
String path = shaders[i];
246
Ref<Resource> res = ResourceLoader::load(path);
247
if (res.is_valid()) {
248
edit(res.ptr());
249
}
250
if (selected_shader == path) {
251
selected_shader_idx = i;
252
}
253
}
254
255
if (p_layout->has_section_key("ShaderEditor", "split_offset")) {
256
files_split->set_split_offset(p_layout->get_value("ShaderEditor", "split_offset"));
257
}
258
259
_update_shader_list();
260
_shader_selected(selected_shader_idx, false);
261
262
_set_text_shader_zoom_factor(p_layout->get_value("ShaderEditor", "text_shader_zoom_factor", 1.0f));
263
264
restoring_layout = false;
265
}
266
267
void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
268
#ifndef DISABLE_DEPRECATED
269
if (p_layout->has_section_key("ShaderEditor", "window_rect")) {
270
p_layout->erase_section_key("ShaderEditor", "window_rect");
271
}
272
if (p_layout->has_section_key("ShaderEditor", "window_screen")) {
273
p_layout->erase_section_key("ShaderEditor", "window_screen");
274
}
275
if (p_layout->has_section_key("ShaderEditor", "window_screen_rect")) {
276
p_layout->erase_section_key("ShaderEditor", "window_screen_rect");
277
}
278
#endif
279
280
Array shaders;
281
String selected_shader;
282
for (int i = 0; i < shader_tabs->get_tab_count(); i++) {
283
EditedShader edited_shader = edited_shaders[i];
284
if (edited_shader.shader_editor) {
285
String shader_path;
286
if (edited_shader.shader.is_valid()) {
287
shader_path = edited_shader.shader->get_path();
288
} else {
289
DEV_ASSERT(edited_shader.shader_inc.is_valid());
290
shader_path = edited_shader.shader_inc->get_path();
291
}
292
shaders.push_back(shader_path);
293
294
ShaderEditor *shader_editor = Object::cast_to<ShaderEditor>(shader_tabs->get_current_tab_control());
295
296
if (shader_editor && edited_shader.shader_editor == shader_editor) {
297
selected_shader = shader_path;
298
}
299
}
300
}
301
p_layout->set_value("ShaderEditor", "open_shaders", shaders);
302
p_layout->set_value("ShaderEditor", "split_offset", files_split->get_split_offset());
303
p_layout->set_value("ShaderEditor", "selected_shader", selected_shader);
304
p_layout->set_value("ShaderEditor", "text_shader_zoom_factor", text_shader_zoom_factor);
305
}
306
307
String ShaderEditorPlugin::get_unsaved_status(const String &p_for_scene) const {
308
// TODO: This should also include visual shaders and shader includes, but save_external_data() doesn't seem to save them...
309
PackedStringArray unsaved_shaders;
310
for (uint32_t i = 0; i < edited_shaders.size(); i++) {
311
if (edited_shaders[i].shader_editor) {
312
if (edited_shaders[i].shader_editor->is_unsaved()) {
313
if (unsaved_shaders.is_empty()) {
314
unsaved_shaders.append(TTR("Save changes to the following shaders(s) before quitting?"));
315
}
316
unsaved_shaders.append(edited_shaders[i].name.trim_suffix("(*)"));
317
}
318
}
319
}
320
321
if (!p_for_scene.is_empty()) {
322
PackedStringArray unsaved_built_in_shaders;
323
324
const String scene_file = p_for_scene.get_file();
325
for (const String &E : unsaved_shaders) {
326
if (!E.is_resource_file() && E.contains(scene_file)) {
327
if (unsaved_built_in_shaders.is_empty()) {
328
unsaved_built_in_shaders.append(TTR("There are unsaved changes in the following built-in shaders(s):"));
329
}
330
unsaved_built_in_shaders.append(E);
331
}
332
}
333
334
if (!unsaved_built_in_shaders.is_empty()) {
335
return String("\n").join(unsaved_built_in_shaders);
336
}
337
return String();
338
}
339
340
return String("\n").join(unsaved_shaders);
341
}
342
343
void ShaderEditorPlugin::save_external_data() {
344
for (EditedShader &edited_shader : edited_shaders) {
345
if (edited_shader.shader_editor && edited_shader.shader_editor->is_unsaved()) {
346
edited_shader.shader_editor->save_external_data();
347
}
348
}
349
_update_shader_list();
350
}
351
352
void ShaderEditorPlugin::apply_changes() {
353
for (EditedShader &edited_shader : edited_shaders) {
354
if (edited_shader.shader_editor) {
355
edited_shader.shader_editor->apply_shaders();
356
}
357
}
358
}
359
360
void ShaderEditorPlugin::_shader_selected(int p_index, bool p_push_item) {
361
if (p_index >= (int)edited_shaders.size()) {
362
return;
363
}
364
365
if (edited_shaders[p_index].shader_editor) {
366
_switch_to_editor(edited_shaders[p_index].shader_editor);
367
edited_shaders[p_index].shader_editor->validate_script();
368
}
369
370
shader_tabs->set_current_tab(p_index);
371
shader_list->select(p_index);
372
373
if (p_push_item) {
374
// Avoid `Shader` being edited when editing `ShaderInclude` due to inspector refreshing.
375
if (edited_shaders[p_index].shader.is_valid()) {
376
EditorNode::get_singleton()->push_item_no_inspector(edited_shaders[p_index].shader.ptr());
377
} else {
378
EditorNode::get_singleton()->push_item_no_inspector(edited_shaders[p_index].shader_inc.ptr());
379
}
380
}
381
}
382
383
void ShaderEditorPlugin::_shader_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {
384
if (p_mouse_button_index == MouseButton::MIDDLE) {
385
_close_shader(p_item);
386
}
387
if (p_mouse_button_index == MouseButton::RIGHT) {
388
_make_script_list_context_menu();
389
}
390
}
391
392
void ShaderEditorPlugin::_setup_popup_menu(PopupMenuType p_type, PopupMenu *p_menu) {
393
if (p_type == FILE) {
394
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/new"), FILE_MENU_NEW);
395
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/new_include"), FILE_MENU_NEW_INCLUDE);
396
p_menu->add_separator();
397
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/open"), FILE_MENU_OPEN);
398
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/open_include"), FILE_MENU_OPEN_INCLUDE);
399
}
400
401
if (p_type == FILE || p_type == CONTEXT_VALID_ITEM) {
402
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save"), FILE_MENU_SAVE);
403
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save_as"), FILE_MENU_SAVE_AS);
404
}
405
406
if (p_type == FILE) {
407
p_menu->add_separator();
408
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/open_in_inspector"), FILE_MENU_INSPECT);
409
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/inspect_native_code"), FILE_MENU_INSPECT_NATIVE_SHADER_CODE);
410
p_menu->add_separator();
411
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_MENU_CLOSE);
412
p_menu->add_separator();
413
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/toggle_files_panel"), FILE_MENU_TOGGLE_FILES_PANEL);
414
} else {
415
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_MENU_CLOSE);
416
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_all"), FILE_MENU_CLOSE_ALL);
417
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_other_tabs"), FILE_MENU_CLOSE_OTHER_TABS);
418
if (p_type == CONTEXT_VALID_ITEM) {
419
p_menu->add_separator();
420
p_menu->add_shortcut(ED_GET_SHORTCUT("shader_editor/copy_path"), FILE_MENU_COPY_PATH);
421
p_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/show_in_file_system"), FILE_MENU_SHOW_IN_FILE_SYSTEM);
422
}
423
}
424
}
425
426
void ShaderEditorPlugin::_make_script_list_context_menu() {
427
context_menu->clear();
428
429
int selected = shader_tabs->get_current_tab();
430
if (selected < 0 || selected >= shader_tabs->get_tab_count()) {
431
return;
432
}
433
434
Control *control = shader_tabs->get_tab_control(selected);
435
bool is_valid_editor_control = Object::cast_to<ShaderEditor>(control) != nullptr;
436
437
_setup_popup_menu(is_valid_editor_control ? CONTEXT_VALID_ITEM : CONTEXT, context_menu);
438
439
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_ALL), shader_tabs->get_tab_count() <= 0);
440
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), shader_tabs->get_tab_count() <= 1);
441
442
context_menu->set_position(files_split->get_screen_position() + files_split->get_local_mouse_position());
443
context_menu->reset_size();
444
context_menu->popup();
445
}
446
447
void ShaderEditorPlugin::_close_shader(int p_index) {
448
ERR_FAIL_INDEX(p_index, shader_tabs->get_tab_count());
449
if (file_menu->get_parent() != nullptr) {
450
file_menu->get_parent()->remove_child(file_menu);
451
}
452
ShaderEditor *shader_editor = Object::cast_to<ShaderEditor>(shader_tabs->get_tab_control(p_index));
453
ERR_FAIL_NULL(shader_editor);
454
455
memdelete(shader_editor);
456
edited_shaders.remove_at(p_index);
457
_update_shader_list();
458
EditorUndoRedoManager::get_singleton()->clear_history(); // To prevent undo on deleted graphs.
459
460
if (shader_tabs->get_tab_count() == 0) {
461
shader_list->show(); // Make sure the panel is visible, because it can't be toggled without open shaders.
462
shader_tabs->hide();
463
files_split->add_child(file_menu);
464
file_menu->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);
465
} else {
466
_switch_to_editor(edited_shaders[shader_tabs->get_current_tab()].shader_editor);
467
}
468
}
469
470
void ShaderEditorPlugin::_close_builtin_shaders_from_scene(const String &p_scene) {
471
for (uint32_t i = 0; i < edited_shaders.size();) {
472
Ref<Shader> &shader = edited_shaders[i].shader;
473
if (shader.is_valid()) {
474
if (shader->is_built_in() && shader->get_path().begins_with(p_scene)) {
475
_close_shader(i);
476
continue;
477
}
478
}
479
Ref<ShaderInclude> &include = edited_shaders[i].shader_inc;
480
if (include.is_valid()) {
481
if (include->is_built_in() && include->get_path().begins_with(p_scene)) {
482
_close_shader(i);
483
continue;
484
}
485
}
486
i++;
487
}
488
}
489
490
void ShaderEditorPlugin::_resource_saved(Object *obj) {
491
// May have been renamed on save.
492
for (EditedShader &edited_shader : edited_shaders) {
493
if (edited_shader.shader.ptr() == obj || edited_shader.shader_inc.ptr() == obj) {
494
_update_shader_list();
495
return;
496
}
497
}
498
}
499
500
void ShaderEditorPlugin::_menu_item_pressed(int p_index) {
501
switch (p_index) {
502
case FILE_MENU_NEW: {
503
String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir();
504
shader_create_dialog->config(base_path.path_join("new_shader"), false, false, "Shader");
505
shader_create_dialog->popup_centered();
506
} break;
507
case FILE_MENU_NEW_INCLUDE: {
508
String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir();
509
shader_create_dialog->config(base_path.path_join("new_shader"), false, false, "ShaderInclude");
510
shader_create_dialog->popup_centered();
511
} break;
512
case FILE_MENU_OPEN: {
513
InspectorDock::get_singleton()->open_resource("Shader");
514
} break;
515
case FILE_MENU_OPEN_INCLUDE: {
516
InspectorDock::get_singleton()->open_resource("ShaderInclude");
517
} break;
518
case FILE_MENU_SAVE: {
519
int index = shader_tabs->get_current_tab();
520
ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());
521
TextShaderEditor *editor = Object::cast_to<TextShaderEditor>(edited_shaders[index].shader_editor);
522
if (editor) {
523
if (editor->get_trim_trailing_whitespace_on_save()) {
524
editor->trim_trailing_whitespace();
525
}
526
527
if (editor->get_trim_final_newlines_on_save()) {
528
editor->trim_final_newlines();
529
}
530
}
531
if (edited_shaders[index].shader.is_valid()) {
532
EditorNode::get_singleton()->save_resource(edited_shaders[index].shader);
533
} else {
534
EditorNode::get_singleton()->save_resource(edited_shaders[index].shader_inc);
535
}
536
if (editor) {
537
editor->tag_saved_version();
538
}
539
} break;
540
case FILE_MENU_SAVE_AS: {
541
int index = shader_tabs->get_current_tab();
542
ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());
543
TextShaderEditor *editor = Object::cast_to<TextShaderEditor>(edited_shaders[index].shader_editor);
544
if (editor) {
545
if (editor->get_trim_trailing_whitespace_on_save()) {
546
editor->trim_trailing_whitespace();
547
}
548
549
if (editor->get_trim_final_newlines_on_save()) {
550
editor->trim_final_newlines();
551
}
552
}
553
String path;
554
if (edited_shaders[index].shader.is_valid()) {
555
path = edited_shaders[index].shader->get_path();
556
if (!path.is_resource_file()) {
557
path = "";
558
}
559
EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader, path);
560
} else {
561
path = edited_shaders[index].shader_inc->get_path();
562
if (!path.is_resource_file()) {
563
path = "";
564
}
565
EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader_inc, path);
566
}
567
if (editor) {
568
editor->tag_saved_version();
569
}
570
} break;
571
case FILE_MENU_INSPECT: {
572
int index = shader_tabs->get_current_tab();
573
ERR_FAIL_INDEX(index, shader_tabs->get_tab_count());
574
if (edited_shaders[index].shader.is_valid()) {
575
EditorNode::get_singleton()->push_item(edited_shaders[index].shader.ptr());
576
} else {
577
EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr());
578
}
579
} break;
580
case FILE_MENU_INSPECT_NATIVE_SHADER_CODE: {
581
int index = shader_tabs->get_current_tab();
582
if (edited_shaders[index].shader.is_valid()) {
583
edited_shaders[index].shader->inspect_native_shader_code();
584
}
585
} break;
586
case FILE_MENU_CLOSE: {
587
_close_shader(shader_tabs->get_current_tab());
588
} break;
589
case FILE_MENU_CLOSE_ALL: {
590
while (shader_tabs->get_tab_count() > 0) {
591
_close_shader(0);
592
}
593
} break;
594
case FILE_MENU_CLOSE_OTHER_TABS: {
595
int index = shader_tabs->get_current_tab();
596
for (int i = 0; i < index; i++) {
597
_close_shader(0);
598
}
599
while (shader_tabs->get_tab_count() > 1) {
600
_close_shader(1);
601
}
602
} break;
603
case FILE_MENU_SHOW_IN_FILE_SYSTEM: {
604
Ref<Resource> shader = _get_current_shader();
605
String path = shader->get_path();
606
if (!path.is_empty()) {
607
FileSystemDock::get_singleton()->navigate_to_path(path);
608
}
609
} break;
610
case FILE_MENU_COPY_PATH: {
611
Ref<Resource> shader = _get_current_shader();
612
DisplayServer::get_singleton()->clipboard_set(shader->get_path());
613
} break;
614
case FILE_MENU_TOGGLE_FILES_PANEL: {
615
shader_list->set_visible(!shader_list->is_visible());
616
617
int index = shader_tabs->get_current_tab();
618
if (index != -1) {
619
ERR_FAIL_INDEX(index, (int)edited_shaders.size());
620
ShaderEditor *shader_editor = edited_shaders[index].shader_editor;
621
ERR_FAIL_NULL(shader_editor);
622
shader_editor->update_toggle_files_button();
623
}
624
} break;
625
}
626
}
627
628
void ShaderEditorPlugin::_shader_created(Ref<Shader> p_shader) {
629
EditorNode::get_singleton()->push_item(p_shader.ptr());
630
}
631
632
void ShaderEditorPlugin::_shader_include_created(Ref<ShaderInclude> p_shader_inc) {
633
EditorNode::get_singleton()->push_item(p_shader_inc.ptr());
634
}
635
636
Variant ShaderEditorPlugin::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
637
if (shader_list->get_item_count() == 0) {
638
return Variant();
639
}
640
641
int idx = 0;
642
if (p_point == Vector2(Math::INF, Math::INF)) {
643
if (shader_list->is_anything_selected()) {
644
idx = shader_list->get_selected_items()[0];
645
}
646
} else {
647
idx = shader_list->get_item_at_position(p_point);
648
}
649
if (idx < 0) {
650
return Variant();
651
}
652
653
HBoxContainer *drag_preview = memnew(HBoxContainer);
654
String preview_name = shader_list->get_item_text(idx);
655
Ref<Texture2D> preview_icon = shader_list->get_item_icon(idx);
656
657
if (preview_icon.is_valid()) {
658
TextureRect *tf = memnew(TextureRect);
659
tf->set_texture(preview_icon);
660
tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
661
drag_preview->add_child(tf);
662
}
663
Label *label = memnew(Label(preview_name));
664
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate script names.
665
drag_preview->add_child(label);
666
files_split->set_drag_preview(drag_preview);
667
668
Dictionary drag_data;
669
drag_data["type"] = "shader_list_element";
670
drag_data["shader_list_element"] = idx;
671
672
Ref<Resource> shader = edited_shaders[idx].shader;
673
if (shader.is_null()) {
674
shader = edited_shaders[idx].shader_inc;
675
}
676
drag_data["file_path"] = shader->get_path();
677
678
return drag_data;
679
}
680
681
bool ShaderEditorPlugin::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
682
Dictionary d = p_data;
683
if (!d.has("type")) {
684
return false;
685
}
686
687
if (String(d["type"]) == "shader_list_element") {
688
return true;
689
}
690
691
if (String(d["type"]) == "files") {
692
Vector<String> files = d["files"];
693
694
if (files.is_empty()) {
695
return false;
696
}
697
698
for (int i = 0; i < files.size(); i++) {
699
const String &file = files[i];
700
if (ResourceLoader::exists(file, "Shader")) {
701
Ref<Shader> shader = ResourceLoader::load(file);
702
if (shader.is_valid()) {
703
return true;
704
}
705
}
706
if (ResourceLoader::exists(file, "ShaderInclude")) {
707
Ref<ShaderInclude> sinclude = ResourceLoader::load(file);
708
if (sinclude.is_valid()) {
709
return true;
710
}
711
}
712
}
713
return false;
714
}
715
716
return false;
717
}
718
719
void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
720
if (!can_drop_data_fw(p_point, p_data, p_from)) {
721
return;
722
}
723
724
Dictionary d = p_data;
725
if (!d.has("type")) {
726
return;
727
}
728
729
if (String(d["type"]) == "shader_list_element") {
730
int idx = d["shader_list_element"];
731
int new_idx = 0;
732
if (p_point == Vector2(Math::INF, Math::INF)) {
733
if (shader_list->is_anything_selected()) {
734
new_idx = shader_list->get_selected_items()[0];
735
}
736
} else {
737
new_idx = shader_list->get_item_at_position(p_point);
738
}
739
_move_shader_tab(idx, new_idx);
740
return;
741
}
742
743
if (String(d["type"]) == "files") {
744
Vector<String> files = d["files"];
745
746
for (int i = 0; i < files.size(); i++) {
747
const String &file = files[i];
748
Ref<Resource> res;
749
if (ResourceLoader::exists(file, "Shader") || ResourceLoader::exists(file, "ShaderInclude")) {
750
res = ResourceLoader::load(file);
751
}
752
if (res.is_valid()) {
753
edit(res.ptr());
754
}
755
}
756
}
757
}
758
759
void ShaderEditorPlugin::_set_text_shader_zoom_factor(float p_zoom_factor) {
760
if (text_shader_zoom_factor == p_zoom_factor) {
761
return;
762
}
763
764
text_shader_zoom_factor = p_zoom_factor;
765
}
766
767
void ShaderEditorPlugin::_update_shader_editor_zoom_factor(CodeTextEditor *p_shader_editor) const {
768
if (p_shader_editor && p_shader_editor->is_visible_in_tree() && text_shader_zoom_factor != p_shader_editor->get_zoom_factor()) {
769
p_shader_editor->set_zoom_factor(text_shader_zoom_factor);
770
}
771
}
772
773
void ShaderEditorPlugin::_switch_to_editor(ShaderEditor *p_editor, bool p_focus) {
774
ERR_FAIL_NULL(p_editor);
775
if (file_menu->get_parent() != nullptr) {
776
file_menu->get_parent()->remove_child(file_menu);
777
}
778
779
shader_tabs->show();
780
p_editor->use_menu_bar(file_menu);
781
file_menu->set_v_size_flags(Control::SIZE_EXPAND_FILL);
782
783
if (p_focus) {
784
TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(p_editor);
785
if (text_shader_editor) {
786
text_shader_editor->get_code_editor()->get_text_editor()->grab_focus();
787
}
788
}
789
}
790
791
void ShaderEditorPlugin::_file_removed(const String &p_removed_file) {
792
for (uint32_t i = 0; i < edited_shaders.size(); i++) {
793
if (edited_shaders[i].path == p_removed_file) {
794
_close_shader(i);
795
break;
796
}
797
}
798
}
799
800
void ShaderEditorPlugin::_res_saved_callback(const Ref<Resource> &p_res) {
801
if (p_res.is_null()) {
802
return;
803
}
804
const String &path = p_res->get_path();
805
806
for (EditedShader &edited : edited_shaders) {
807
Ref<Resource> shader_res = edited.shader;
808
if (shader_res.is_null()) {
809
shader_res = edited.shader_inc;
810
}
811
ERR_FAIL_COND(shader_res.is_null());
812
813
TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(edited.shader_editor);
814
if (!text_shader_editor || !shader_res->is_built_in()) {
815
continue;
816
}
817
818
if (shader_res->get_path().get_slice("::", 0) == path) {
819
text_shader_editor->tag_saved_version();
820
_update_shader_list();
821
}
822
}
823
}
824
825
void ShaderEditorPlugin::_set_file_specific_items_disabled(bool p_disabled) {
826
PopupMenu *file_popup_menu = file_menu->get_popup();
827
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_SAVE), p_disabled);
828
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_SAVE_AS), p_disabled);
829
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_INSPECT), p_disabled);
830
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_INSPECT_NATIVE_SHADER_CODE), p_disabled);
831
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_MENU_CLOSE), p_disabled);
832
}
833
834
void ShaderEditorPlugin::_notification(int p_what) {
835
switch (p_what) {
836
case NOTIFICATION_READY: {
837
EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED);
838
EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ShaderEditorPlugin::_close_builtin_shaders_from_scene));
839
FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ShaderEditorPlugin::_file_removed));
840
EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ShaderEditorPlugin::_res_saved_callback));
841
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list));
842
} break;
843
}
844
}
845
846
void ShaderEditorPlugin::shortcut_input(const Ref<InputEvent> &p_event) {
847
if (p_event.is_null() || !p_event->is_pressed() || p_event->is_echo()) {
848
return;
849
}
850
851
if (make_floating_shortcut.is_valid() && make_floating_shortcut->matches_event(p_event)) {
852
shader_dock->make_floating();
853
}
854
}
855
856
ShaderEditorPlugin::ShaderEditorPlugin() {
857
ED_SHORTCUT("shader_editor/new", TTRC("New Shader..."), KeyModifierMask::CMD_OR_CTRL | Key::N);
858
ED_SHORTCUT("shader_editor/new_include", TTRC("New Shader Include..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N);
859
ED_SHORTCUT("shader_editor/open", TTRC("Load Shader File..."), KeyModifierMask::CMD_OR_CTRL | Key::O);
860
ED_SHORTCUT("shader_editor/open_include", TTRC("Load Shader Include File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O);
861
ED_SHORTCUT("shader_editor/open_in_inspector", TTRC("Open File in Inspector"));
862
ED_SHORTCUT("shader_editor/inspect_native_code", TTRC("Inspect Native Shader Code..."));
863
ED_SHORTCUT("shader_editor/copy_path", TTRC("Copy Shader Path"));
864
865
shader_dock = memnew(EditorDock);
866
shader_dock->set_name(TTRC("Shader Editor"));
867
shader_dock->set_icon_name("ShaderDock");
868
shader_dock->set_dock_shortcut(ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTRC("Toggle Shader Editor Dock"), KeyModifierMask::ALT | Key::S));
869
shader_dock->set_default_slot(EditorDock::DOCK_SLOT_BOTTOM);
870
shader_dock->set_available_layouts(EditorDock::DOCK_LAYOUT_HORIZONTAL | EditorDock::DOCK_LAYOUT_FLOATING);
871
shader_dock->set_custom_minimum_size(Size2(460, 300) * EDSCALE);
872
EditorDockManager::get_singleton()->add_dock(shader_dock);
873
874
set_process_shortcut_input(true);
875
876
make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTRC("Make Floating"));
877
878
files_split = memnew(HSplitContainer);
879
files_split->set_split_offset(200 * EDSCALE);
880
files_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
881
shader_dock->add_child(files_split);
882
883
context_menu = memnew(PopupMenu);
884
context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
885
add_child(context_menu);
886
887
shader_list = memnew(ItemList);
888
shader_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
889
shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
890
shader_list->set_theme_type_variation("ItemListSecondary");
891
shader_list->set_custom_minimum_size(Size2(100, 60) * EDSCALE);
892
shader_list->connect(SceneStringName(item_selected), callable_mp(this, &ShaderEditorPlugin::_shader_selected).bind(true));
893
shader_list->connect("item_clicked", callable_mp(this, &ShaderEditorPlugin::_shader_list_clicked));
894
shader_list->set_allow_rmb_select(true);
895
SET_DRAG_FORWARDING_GCD(shader_list, ShaderEditorPlugin);
896
files_split->add_child(shader_list);
897
898
shader_tabs = memnew(TabContainer);
899
shader_tabs->set_tabs_visible(false);
900
shader_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
901
shader_tabs->set_v_size_flags(Control::SIZE_EXPAND_FILL);
902
Ref<StyleBoxEmpty> empty;
903
empty.instantiate();
904
shader_tabs->add_theme_style_override(SceneStringName(panel), empty);
905
shader_tabs->hide();
906
files_split->add_child(shader_tabs);
907
908
file_menu = memnew(MenuButton);
909
file_menu->set_flat(false);
910
file_menu->set_theme_type_variation("FlatMenuButton");
911
file_menu->set_text(TTRC("File"));
912
file_menu->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
913
file_menu->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);
914
file_menu->set_switch_on_hover(true);
915
file_menu->set_shortcut_context(files_split);
916
file_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
917
_setup_popup_menu(FILE, file_menu->get_popup());
918
_set_file_specific_items_disabled(true);
919
files_split->add_child(file_menu);
920
921
shader_create_dialog = memnew(ShaderCreateDialog);
922
shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created));
923
shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created));
924
shader_dock->add_child(shader_create_dialog);
925
926
Ref<TextShaderLanguagePlugin> text_shader_lang;
927
text_shader_lang.instantiate();
928
EditorShaderLanguagePlugin::register_shader_language(text_shader_lang);
929
930
Ref<VisualShaderLanguagePlugin> visual_shader_lang;
931
visual_shader_lang.instantiate();
932
EditorShaderLanguagePlugin::register_shader_language(visual_shader_lang);
933
}
934
935
ShaderEditorPlugin::~ShaderEditorPlugin() {
936
EditorShaderLanguagePlugin::clear_registered_shader_languages();
937
memdelete(file_menu);
938
}
939
940