Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/shader/text_shader_editor.cpp
20852 views
1
/**************************************************************************/
2
/* text_shader_editor.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 "text_shader_editor.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/version_generated.gen.h"
35
#include "editor/editor_node.h"
36
#include "editor/editor_string_names.h"
37
#include "editor/file_system/editor_file_system.h"
38
#include "editor/settings/editor_settings.h"
39
#include "editor/themes/editor_scale.h"
40
#include "editor/themes/editor_theme_manager.h"
41
#include "scene/gui/split_container.h"
42
#include "servers/rendering/shader_preprocessor.h"
43
#include "servers/rendering/shader_types.h"
44
45
/*** SHADER SYNTAX HIGHLIGHTER ****/
46
47
Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
48
Dictionary color_map;
49
50
for (const Point2i &region : disabled_branch_regions) {
51
if (p_line >= region.x && p_line <= region.y) {
52
// When "color_regions[0].p_start_key.length() > 2",
53
// disabled_branch_region causes color_region to break.
54
// This should be seen as a temporary solution.
55
CodeHighlighter::_get_line_syntax_highlighting_impl(p_line);
56
57
Dictionary highlighter_info;
58
highlighter_info["color"] = disabled_branch_color;
59
60
color_map[0] = highlighter_info;
61
return color_map;
62
}
63
}
64
65
return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line);
66
}
67
68
void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) {
69
ERR_FAIL_COND(p_region.x < 0);
70
ERR_FAIL_COND(p_region.y < 0);
71
72
for (int i = 0; i < disabled_branch_regions.size(); i++) {
73
ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists.");
74
}
75
76
Point2i disabled_branch_region;
77
disabled_branch_region.x = p_region.x;
78
disabled_branch_region.y = p_region.y;
79
disabled_branch_regions.push_back(disabled_branch_region);
80
81
clear_highlighting_cache();
82
}
83
84
void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() {
85
disabled_branch_regions.clear();
86
clear_highlighting_cache();
87
}
88
89
void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) {
90
disabled_branch_color = p_color;
91
clear_highlighting_cache();
92
}
93
94
/*** SHADER SCRIPT EDITOR ****/
95
96
static bool saved_warnings_enabled = false;
97
static bool saved_treat_warning_as_errors = false;
98
static HashMap<ShaderWarning::Code, bool> saved_warnings;
99
static uint32_t saved_warning_flags = 0U;
100
101
void ShaderTextEditor::_notification(int p_what) {
102
switch (p_what) {
103
case NOTIFICATION_THEME_CHANGED: {
104
if (is_visible_in_tree()) {
105
_load_theme_settings();
106
if (warnings.size() > 0 && last_compile_result == OK) {
107
warnings_panel->clear();
108
_update_warning_panel();
109
}
110
}
111
} break;
112
}
113
}
114
115
Ref<Shader> ShaderTextEditor::get_edited_shader() const {
116
return shader;
117
}
118
119
Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const {
120
return shader_inc;
121
}
122
123
void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) {
124
set_edited_shader(p_shader, p_shader->get_code());
125
}
126
127
void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) {
128
if (shader == p_shader) {
129
return;
130
}
131
if (shader.is_valid()) {
132
shader->disconnect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
133
}
134
shader = p_shader;
135
shader_inc = Ref<ShaderInclude>();
136
137
set_edited_code(p_code);
138
139
if (shader.is_valid()) {
140
shader->connect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
141
}
142
}
143
144
void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) {
145
set_edited_shader_include(p_shader_inc, p_shader_inc->get_code());
146
}
147
148
void ShaderTextEditor::_shader_changed() {
149
// This function is used for dependencies (include changing changes main shader and forces it to revalidate)
150
if (block_shader_changed) {
151
return;
152
}
153
dependencies_version++;
154
_validate_script();
155
}
156
157
void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) {
158
if (shader_inc == p_shader_inc) {
159
return;
160
}
161
if (shader_inc.is_valid()) {
162
shader_inc->disconnect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
163
}
164
shader_inc = p_shader_inc;
165
shader = Ref<Shader>();
166
167
set_edited_code(p_code);
168
169
if (shader_inc.is_valid()) {
170
shader_inc->connect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
171
}
172
}
173
174
void ShaderTextEditor::set_edited_code(const String &p_code) {
175
_load_theme_settings();
176
177
get_text_editor()->set_text(p_code);
178
get_text_editor()->clear_undo_history();
179
callable_mp((TextEdit *)get_text_editor(), &TextEdit::set_h_scroll).call_deferred(0);
180
callable_mp((TextEdit *)get_text_editor(), &TextEdit::set_v_scroll).call_deferred(0);
181
get_text_editor()->tag_saved_version();
182
183
_validate_script();
184
_line_col_changed();
185
}
186
187
void ShaderTextEditor::reload_text() {
188
ERR_FAIL_COND(shader.is_null() && shader_inc.is_null());
189
190
String code;
191
if (shader.is_valid()) {
192
code = shader->get_code();
193
} else {
194
code = shader_inc->get_code();
195
}
196
197
CodeEdit *te = get_text_editor();
198
int column = te->get_caret_column();
199
int row = te->get_caret_line();
200
int h = te->get_h_scroll();
201
int v = te->get_v_scroll();
202
203
te->set_text(code);
204
te->set_caret_line(row);
205
te->set_caret_column(column);
206
te->set_h_scroll(h);
207
te->set_v_scroll(v);
208
209
te->tag_saved_version();
210
211
update_line_and_column();
212
}
213
214
void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
215
warnings_panel = p_warnings_panel;
216
}
217
218
void ShaderTextEditor::_load_theme_settings() {
219
CodeEdit *te = get_text_editor();
220
Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color");
221
if (updated_marked_line_color != marked_line_color) {
222
for (int i = 0; i < te->get_line_count(); i++) {
223
if (te->get_line_background_color(i) == marked_line_color) {
224
te->set_line_background_color(i, updated_marked_line_color);
225
}
226
}
227
marked_line_color = updated_marked_line_color;
228
}
229
230
syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
231
syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
232
syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));
233
syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));
234
235
syntax_highlighter->clear_keyword_colors();
236
237
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
238
const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
239
240
List<String> keywords;
241
ShaderLanguage::get_keyword_list(&keywords);
242
243
for (const String &E : keywords) {
244
if (ShaderLanguage::is_control_flow_keyword(E)) {
245
syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
246
} else {
247
syntax_highlighter->add_keyword_color(E, keyword_color);
248
}
249
}
250
251
List<String> pp_keywords;
252
ShaderPreprocessor::get_keyword_list(&pp_keywords, false);
253
254
for (const String &E : pp_keywords) {
255
syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
256
}
257
258
// Colorize built-ins like `COLOR` differently to make them easier
259
// to distinguish from keywords at a quick glance.
260
261
List<String> built_ins;
262
263
if (shader_inc.is_valid()) {
264
for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
265
for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) {
266
for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
267
built_ins.push_back(F.key);
268
}
269
}
270
271
{
272
const Vector<ShaderLanguage::ModeInfo> &render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
273
274
for (const ShaderLanguage::ModeInfo &mode_info : render_modes) {
275
if (!mode_info.options.is_empty()) {
276
for (const StringName &option : mode_info.options) {
277
built_ins.push_back(String(mode_info.name) + "_" + String(option));
278
}
279
} else {
280
built_ins.push_back(String(mode_info.name));
281
}
282
}
283
}
284
285
{
286
const Vector<ShaderLanguage::ModeInfo> &stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(i));
287
288
for (const ShaderLanguage::ModeInfo &mode_info : stencil_modes) {
289
if (!mode_info.options.is_empty()) {
290
for (const StringName &option : mode_info.options) {
291
built_ins.push_back(String(mode_info.name) + "_" + String(option));
292
}
293
} else {
294
built_ins.push_back(String(mode_info.name));
295
}
296
}
297
}
298
}
299
} else if (shader.is_valid()) {
300
for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) {
301
for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
302
built_ins.push_back(F.key);
303
}
304
}
305
306
{
307
const Vector<ShaderLanguage::ModeInfo> &shader_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
308
309
for (const ShaderLanguage::ModeInfo &mode_info : shader_modes) {
310
if (!mode_info.options.is_empty()) {
311
for (const StringName &option : mode_info.options) {
312
built_ins.push_back(String(mode_info.name) + "_" + String(option));
313
}
314
} else {
315
built_ins.push_back(String(mode_info.name));
316
}
317
}
318
}
319
320
{
321
const Vector<ShaderLanguage::ModeInfo> &stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader->get_mode()));
322
323
for (const ShaderLanguage::ModeInfo &mode_info : stencil_modes) {
324
if (!mode_info.options.is_empty()) {
325
for (const StringName &option : mode_info.options) {
326
built_ins.push_back(String(mode_info.name) + "_" + String(option));
327
}
328
} else {
329
built_ins.push_back(String(mode_info.name));
330
}
331
}
332
}
333
}
334
335
const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
336
337
for (const String &E : built_ins) {
338
syntax_highlighter->add_keyword_color(E, user_type_color);
339
}
340
341
// Colorize comments.
342
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
343
syntax_highlighter->clear_color_regions();
344
syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
345
syntax_highlighter->add_color_region("//", "", comment_color, true);
346
347
const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
348
syntax_highlighter->add_color_region("/**", "*/", doc_comment_color, false);
349
// "/**/" will be treated as the start of the "/**" region, this line is guaranteed to end the color_region.
350
syntax_highlighter->add_color_region("/**/", "", comment_color, true);
351
352
// Disabled preprocessor branches use translucent text color to be easier to distinguish from comments.
353
syntax_highlighter->set_disabled_branch_color(Color(EDITOR_GET("text_editor/theme/highlighting/text_color")) * Color(1, 1, 1, 0.5));
354
355
te->clear_comment_delimiters();
356
te->add_comment_delimiter("/*", "*/", false);
357
te->add_comment_delimiter("//", "", true);
358
359
if (!te->has_auto_brace_completion_open_key("/*")) {
360
te->add_auto_brace_completion_pair("/*", "*/");
361
}
362
363
// Colorize preprocessor include strings.
364
const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
365
syntax_highlighter->add_color_region("\"", "\"", string_color, false);
366
syntax_highlighter->set_uint_suffix_enabled(true);
367
}
368
369
void ShaderTextEditor::_check_shader_mode() {
370
String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text());
371
372
Shader::Mode mode;
373
374
if (type == "canvas_item") {
375
mode = Shader::MODE_CANVAS_ITEM;
376
} else if (type == "particles") {
377
mode = Shader::MODE_PARTICLES;
378
} else if (type == "sky") {
379
mode = Shader::MODE_SKY;
380
} else if (type == "fog") {
381
mode = Shader::MODE_FOG;
382
} else if (type == "texture_blit") {
383
mode = Shader::MODE_TEXTURE_BLIT;
384
} else {
385
mode = Shader::MODE_SPATIAL;
386
}
387
388
if (shader->get_mode() != mode) {
389
set_block_shader_changed(true);
390
shader->set_code(get_text_editor()->get_text());
391
set_block_shader_changed(false);
392
_load_theme_settings();
393
}
394
}
395
396
static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) {
397
RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable);
398
return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt);
399
}
400
401
static String complete_from_path;
402
403
static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) {
404
if (!p_efsd) {
405
return;
406
}
407
for (int i = 0; i < p_efsd->get_file_count(); i++) {
408
if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) {
409
String path = p_efsd->get_file_path(i);
410
if (path.begins_with(complete_from_path)) {
411
path = path.replace_first(complete_from_path, "");
412
}
413
r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH));
414
}
415
}
416
for (int j = 0; j < p_efsd->get_subdir_count(); j++) {
417
_complete_include_paths_search(p_efsd->get_subdir(j), r_options);
418
}
419
}
420
421
static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) {
422
_complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options);
423
}
424
425
void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {
426
List<ScriptLanguage::CodeCompletionOption> pp_options;
427
List<ScriptLanguage::CodeCompletionOption> pp_defines;
428
ShaderPreprocessor preprocessor;
429
String code;
430
String resource_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path());
431
complete_from_path = resource_path.get_base_dir();
432
if (!complete_from_path.ends_with("/")) {
433
complete_from_path += "/";
434
}
435
preprocessor.preprocess(p_code, resource_path, code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
436
complete_from_path = String();
437
if (pp_options.size()) {
438
for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
439
r_options->push_back(E);
440
}
441
return;
442
}
443
for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) {
444
r_options->push_back(E);
445
}
446
447
ShaderLanguage sl;
448
String calltip;
449
ShaderLanguage::ShaderCompileInfo comp_info;
450
comp_info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
451
452
if (shader.is_null()) {
453
comp_info.is_include = true;
454
455
sl.complete(code, comp_info, r_options, calltip);
456
get_text_editor()->set_code_hint(calltip);
457
return;
458
}
459
_check_shader_mode();
460
comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()));
461
comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
462
comp_info.stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader->get_mode()));
463
comp_info.shader_types = ShaderTypes::get_singleton()->get_types();
464
465
sl.complete(code, comp_info, r_options, calltip);
466
get_text_editor()->set_code_hint(calltip);
467
}
468
469
void ShaderTextEditor::_validate_script() {
470
emit_signal(CoreStringName(script_changed)); // Ensure to notify that it changed, so it is applied
471
472
String code;
473
474
if (shader.is_valid()) {
475
_check_shader_mode();
476
code = shader->get_code();
477
} else {
478
code = shader_inc->get_code();
479
}
480
481
ShaderPreprocessor preprocessor;
482
String code_pp;
483
String error_pp;
484
List<ShaderPreprocessor::FilePosition> err_positions;
485
List<ShaderPreprocessor::Region> regions;
486
String filename;
487
if (shader.is_valid()) {
488
filename = shader->get_path();
489
} else if (shader_inc.is_valid()) {
490
filename = shader_inc->get_path();
491
}
492
last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, &regions);
493
494
for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
495
get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
496
}
497
498
syntax_highlighter->clear_disabled_branch_regions();
499
for (const ShaderPreprocessor::Region &region : regions) {
500
if (!region.enabled) {
501
if (filename != region.file) {
502
continue;
503
}
504
syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line));
505
}
506
}
507
508
set_error("");
509
set_error_count(0);
510
511
if (last_compile_result != OK) {
512
// Preprocessor error.
513
ERR_FAIL_COND(err_positions.is_empty());
514
515
String err_text;
516
const int err_line = err_positions.front()->get().line;
517
if (err_positions.size() == 1) {
518
// Error in the main file.
519
const String message = error_pp.replace("[", "[lb]");
520
521
err_text = vformat(TTR("Error at line %d:"), err_line) + " " + message;
522
} else {
523
// Error in an included file.
524
const String inc_file = err_positions.back()->get().file.get_file();
525
const int inc_line = err_positions.back()->get().line;
526
const String message = error_pp.replace("[", "[lb]");
527
528
err_text = vformat(TTR("Error at line %d in include %s:%d:"), err_line, inc_file, inc_line) + " " + message;
529
set_error_count(err_positions.size() - 1);
530
}
531
532
set_error(err_text);
533
set_error_pos(err_line - 1, 0);
534
535
for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
536
get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
537
}
538
get_text_editor()->set_line_background_color(err_line - 1, marked_line_color);
539
540
set_warning_count(0);
541
} else {
542
ShaderLanguage sl;
543
544
sl.enable_warning_checking(saved_warnings_enabled);
545
uint32_t flags = saved_warning_flags;
546
if (shader.is_null()) {
547
if (flags & ShaderWarning::UNUSED_CONSTANT) {
548
flags &= ~(ShaderWarning::UNUSED_CONSTANT);
549
}
550
if (flags & ShaderWarning::UNUSED_FUNCTION) {
551
flags &= ~(ShaderWarning::UNUSED_FUNCTION);
552
}
553
if (flags & ShaderWarning::UNUSED_STRUCT) {
554
flags &= ~(ShaderWarning::UNUSED_STRUCT);
555
}
556
if (flags & ShaderWarning::UNUSED_UNIFORM) {
557
flags &= ~(ShaderWarning::UNUSED_UNIFORM);
558
}
559
if (flags & ShaderWarning::UNUSED_VARYING) {
560
flags &= ~(ShaderWarning::UNUSED_VARYING);
561
}
562
}
563
sl.set_warning_flags(flags);
564
565
ShaderLanguage::ShaderCompileInfo comp_info;
566
comp_info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
567
568
if (shader.is_null()) {
569
comp_info.is_include = true;
570
} else {
571
Shader::Mode mode = shader->get_mode();
572
comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
573
comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
574
comp_info.stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(mode));
575
comp_info.shader_types = ShaderTypes::get_singleton()->get_types();
576
}
577
578
code = code_pp;
579
//compiler error
580
last_compile_result = sl.compile(code, comp_info);
581
582
if (last_compile_result != OK) {
583
Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
584
585
String err_text;
586
int err_line;
587
if (include_positions.size() > 1) {
588
// Error in an included file.
589
err_line = include_positions[0].line;
590
591
const String inc_file = include_positions[include_positions.size() - 1].file;
592
const int inc_line = include_positions[include_positions.size() - 1].line;
593
const String message = sl.get_error_text().replace("[", "[lb]");
594
595
err_text = vformat(TTR("Error at line %d in include %s:%d:"), err_line, inc_file, inc_line) + " " + message;
596
set_error_count(include_positions.size() - 1);
597
} else {
598
// Error in the main file.
599
err_line = sl.get_error_line();
600
601
const String message = sl.get_error_text().replace("[", "[lb]");
602
603
err_text = vformat(TTR("Error at line %d:"), err_line) + " " + message;
604
set_error_count(0);
605
}
606
607
set_error(err_text);
608
set_error_pos(err_line - 1, 0);
609
610
get_text_editor()->set_line_background_color(err_line - 1, marked_line_color);
611
} else {
612
set_error("");
613
}
614
615
if (warnings.size() > 0 || last_compile_result != OK) {
616
warnings_panel->clear();
617
}
618
warnings.clear();
619
for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
620
warnings.push_back(E->get());
621
}
622
if (warnings.size() > 0 && last_compile_result == OK) {
623
warnings.sort_custom<WarningsComparator>();
624
_update_warning_panel();
625
} else {
626
set_warning_count(0);
627
}
628
}
629
630
emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts
631
}
632
633
void ShaderTextEditor::_update_warning_panel() {
634
int warning_count = 0;
635
636
warnings_panel->push_table(2);
637
for (const ShaderWarning &w : warnings) {
638
if (warning_count == 0) {
639
if (saved_treat_warning_as_errors) {
640
const String message = (w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.")).replace("[", "[lb]");
641
const String error_text = vformat(TTR("Error at line %d:"), w.get_line()) + " " + message;
642
643
set_error(error_text);
644
set_error_pos(w.get_line() - 1, 0);
645
646
get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
647
}
648
}
649
650
warning_count++;
651
int line = w.get_line();
652
653
// First cell.
654
warnings_panel->push_cell();
655
warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
656
if (line != -1) {
657
warnings_panel->push_meta(line - 1);
658
warnings_panel->add_text(vformat(TTR("Line %d (%s):"), line, w.get_name()));
659
warnings_panel->pop(); // Meta goto.
660
} else {
661
warnings_panel->add_text(w.get_name() + ":");
662
}
663
warnings_panel->pop(); // Color.
664
warnings_panel->pop(); // Cell.
665
666
// Second cell.
667
warnings_panel->push_cell();
668
warnings_panel->add_text(w.get_message());
669
warnings_panel->pop(); // Cell.
670
}
671
warnings_panel->pop(); // Table.
672
673
set_warning_count(warning_count);
674
}
675
676
void ShaderTextEditor::_bind_methods() {
677
ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid")));
678
}
679
680
ShaderTextEditor::ShaderTextEditor() {
681
syntax_highlighter.instantiate();
682
get_text_editor()->set_syntax_highlighter(syntax_highlighter);
683
}
684
685
/*** SCRIPT EDITOR ******/
686
687
void TextShaderEditor::_menu_option(int p_option) {
688
code_editor->get_text_editor()->apply_ime();
689
690
switch (p_option) {
691
case EDIT_UNDO: {
692
code_editor->get_text_editor()->undo();
693
} break;
694
case EDIT_REDO: {
695
code_editor->get_text_editor()->redo();
696
} break;
697
case EDIT_CUT: {
698
code_editor->get_text_editor()->cut();
699
} break;
700
case EDIT_COPY: {
701
code_editor->get_text_editor()->copy();
702
} break;
703
case EDIT_PASTE: {
704
code_editor->get_text_editor()->paste();
705
} break;
706
case EDIT_SELECT_ALL: {
707
code_editor->get_text_editor()->select_all();
708
} break;
709
case EDIT_MOVE_LINE_UP: {
710
code_editor->get_text_editor()->move_lines_up();
711
} break;
712
case EDIT_MOVE_LINE_DOWN: {
713
code_editor->get_text_editor()->move_lines_down();
714
} break;
715
case EDIT_INDENT: {
716
if (shader.is_null() && shader_inc.is_null()) {
717
return;
718
}
719
code_editor->get_text_editor()->indent_lines();
720
} break;
721
case EDIT_UNINDENT: {
722
if (shader.is_null() && shader_inc.is_null()) {
723
return;
724
}
725
code_editor->get_text_editor()->unindent_lines();
726
} break;
727
case EDIT_DELETE_LINE: {
728
code_editor->get_text_editor()->delete_lines();
729
} break;
730
case EDIT_DUPLICATE_SELECTION: {
731
code_editor->get_text_editor()->duplicate_selection();
732
} break;
733
case EDIT_DUPLICATE_LINES: {
734
code_editor->get_text_editor()->duplicate_lines();
735
} break;
736
case EDIT_TOGGLE_WORD_WRAP: {
737
TextEdit::LineWrappingMode wrap = code_editor->get_text_editor()->get_line_wrapping_mode();
738
code_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);
739
} break;
740
case EDIT_TOGGLE_COMMENT: {
741
if (shader.is_null() && shader_inc.is_null()) {
742
return;
743
}
744
code_editor->toggle_inline_comment("//");
745
} break;
746
case EDIT_COMPLETE: {
747
code_editor->get_text_editor()->request_code_completion();
748
} break;
749
case SEARCH_FIND: {
750
code_editor->get_find_replace_bar()->popup_search();
751
} break;
752
case SEARCH_FIND_NEXT: {
753
code_editor->get_find_replace_bar()->search_next();
754
} break;
755
case SEARCH_FIND_PREV: {
756
code_editor->get_find_replace_bar()->search_prev();
757
} break;
758
case SEARCH_REPLACE: {
759
code_editor->get_find_replace_bar()->popup_replace();
760
} break;
761
case SEARCH_GOTO_LINE: {
762
goto_line_popup->popup_find_line(code_editor);
763
} break;
764
case BOOKMARK_TOGGLE: {
765
code_editor->toggle_bookmark();
766
} break;
767
case BOOKMARK_GOTO_NEXT: {
768
code_editor->goto_next_bookmark();
769
} break;
770
case BOOKMARK_GOTO_PREV: {
771
code_editor->goto_prev_bookmark();
772
} break;
773
case BOOKMARK_REMOVE_ALL: {
774
code_editor->remove_all_bookmarks();
775
} break;
776
case HELP_DOCS: {
777
OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", GODOT_VERSION_DOCS_URL));
778
} break;
779
case EDIT_EMOJI_AND_SYMBOL: {
780
code_editor->get_text_editor()->show_emoji_and_symbol_picker();
781
} break;
782
}
783
if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
784
callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(false);
785
}
786
}
787
788
void TextShaderEditor::_prepare_edit_menu() {
789
const CodeEdit *tx = code_editor->get_text_editor();
790
PopupMenu *popup = edit_menu->get_popup();
791
popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo());
792
popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo());
793
}
794
795
void TextShaderEditor::_notification(int p_what) {
796
switch (p_what) {
797
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
798
if (EditorThemeManager::is_generated_theme_outdated() ||
799
EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor") ||
800
EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor")) {
801
_apply_editor_settings();
802
}
803
} break;
804
805
case NOTIFICATION_THEME_CHANGED: {
806
site_search->set_button_icon(get_editor_theme_icon(SNAME("ExternalLink")));
807
} break;
808
809
case NOTIFICATION_APPLICATION_FOCUS_IN: {
810
_check_for_external_edit();
811
} break;
812
}
813
}
814
815
void TextShaderEditor::_apply_editor_settings() {
816
code_editor->update_editor_settings();
817
818
trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
819
trim_final_newlines_on_save = EDITOR_GET("text_editor/behavior/files/trim_final_newlines_on_save");
820
}
821
822
void TextShaderEditor::_show_warnings_panel(bool p_show) {
823
warnings_panel->set_visible(p_show);
824
}
825
826
void TextShaderEditor::_warning_clicked(const Variant &p_line) {
827
if (p_line.get_type() == Variant::INT) {
828
code_editor->goto_line_centered(p_line.operator int64_t());
829
}
830
}
831
832
void TextShaderEditor::_bind_methods() {
833
ClassDB::bind_method("_show_warnings_panel", &TextShaderEditor::_show_warnings_panel);
834
ClassDB::bind_method("_warning_clicked", &TextShaderEditor::_warning_clicked);
835
836
ADD_SIGNAL(MethodInfo("validation_changed"));
837
}
838
839
void TextShaderEditor::ensure_select_current() {
840
}
841
842
void TextShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
843
code_editor->goto_line_selection(p_line, p_begin, p_end);
844
}
845
846
void TextShaderEditor::_project_settings_changed() {
847
_update_warnings(true);
848
}
849
850
void TextShaderEditor::_update_warnings(bool p_validate) {
851
bool changed = false;
852
853
bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
854
if (warnings_enabled != saved_warnings_enabled) {
855
saved_warnings_enabled = warnings_enabled;
856
changed = true;
857
}
858
859
bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
860
if (treat_warning_as_errors != saved_treat_warning_as_errors) {
861
saved_treat_warning_as_errors = treat_warning_as_errors;
862
changed = true;
863
}
864
865
bool update_flags = false;
866
867
for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
868
ShaderWarning::Code code = (ShaderWarning::Code)i;
869
bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
870
871
if (saved_warnings[code] != value) {
872
saved_warnings[code] = value;
873
update_flags = true;
874
changed = true;
875
}
876
}
877
878
if (update_flags) {
879
saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
880
}
881
882
if (p_validate && changed && code_editor && code_editor->get_edited_shader().is_valid()) {
883
code_editor->validate_script();
884
}
885
}
886
887
void TextShaderEditor::_check_for_external_edit() {
888
bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"));
889
890
if (shader_inc.is_valid()) {
891
if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) {
892
if (use_autoreload) {
893
_reload_shader_include_from_disk();
894
} else {
895
callable_mp((Window *)disk_changed, &Window::popup_centered).call_deferred(Size2i());
896
}
897
}
898
return;
899
}
900
901
if (shader.is_null() || shader->is_built_in()) {
902
return;
903
}
904
905
if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) {
906
if (use_autoreload) {
907
_reload_shader_from_disk();
908
} else {
909
callable_mp((Window *)disk_changed, &Window::popup_centered).call_deferred(Size2i());
910
}
911
}
912
}
913
914
void TextShaderEditor::_reload_shader_from_disk() {
915
Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
916
ERR_FAIL_COND(rel_shader.is_null());
917
918
code_editor->set_block_shader_changed(true);
919
shader->set_code(rel_shader->get_code());
920
code_editor->set_block_shader_changed(false);
921
shader->set_last_modified_time(rel_shader->get_last_modified_time());
922
code_editor->reload_text();
923
}
924
925
void TextShaderEditor::_reload_shader_include_from_disk() {
926
Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
927
ERR_FAIL_COND(rel_shader_include.is_null());
928
929
code_editor->set_block_shader_changed(true);
930
shader_inc->set_code(rel_shader_include->get_code());
931
code_editor->set_block_shader_changed(false);
932
shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time());
933
code_editor->reload_text();
934
}
935
936
void TextShaderEditor::_reload() {
937
if (shader.is_valid()) {
938
_reload_shader_from_disk();
939
} else if (shader_inc.is_valid()) {
940
_reload_shader_include_from_disk();
941
}
942
}
943
944
void TextShaderEditor::edit_shader(const Ref<Shader> &p_shader) {
945
if (p_shader.is_null() || !p_shader->is_text_shader()) {
946
return;
947
}
948
949
if (shader == p_shader) {
950
return;
951
}
952
953
shader = p_shader;
954
shader_inc = Ref<ShaderInclude>();
955
956
code_editor->set_edited_shader(shader);
957
}
958
959
void TextShaderEditor::edit_shader_include(const Ref<ShaderInclude> &p_shader_inc) {
960
if (p_shader_inc.is_null()) {
961
return;
962
}
963
964
if (shader_inc == p_shader_inc) {
965
return;
966
}
967
968
shader_inc = p_shader_inc;
969
shader = Ref<Shader>();
970
971
code_editor->set_edited_shader_include(p_shader_inc);
972
}
973
974
void TextShaderEditor::use_menu_bar(MenuButton *p_file_menu) {
975
p_file_menu->set_switch_on_hover(true);
976
menu_bar_hbox->add_child(p_file_menu);
977
menu_bar_hbox->move_child(p_file_menu, 0);
978
}
979
980
void TextShaderEditor::save_external_data(const String &p_str) {
981
if (shader.is_null() && shader_inc.is_null()) {
982
disk_changed->hide();
983
return;
984
}
985
986
if (trim_trailing_whitespace_on_save) {
987
trim_trailing_whitespace();
988
}
989
990
if (trim_final_newlines_on_save) {
991
trim_final_newlines();
992
}
993
994
apply_shaders();
995
996
Ref<Shader> edited_shader = code_editor->get_edited_shader();
997
if (edited_shader.is_valid()) {
998
ResourceSaver::save(edited_shader);
999
}
1000
if (shader.is_valid() && shader != edited_shader) {
1001
ResourceSaver::save(shader);
1002
}
1003
1004
Ref<ShaderInclude> edited_shader_inc = code_editor->get_edited_shader_include();
1005
if (edited_shader_inc.is_valid()) {
1006
ResourceSaver::save(edited_shader_inc);
1007
}
1008
if (shader_inc.is_valid() && shader_inc != edited_shader_inc) {
1009
ResourceSaver::save(shader_inc);
1010
}
1011
code_editor->get_text_editor()->tag_saved_version();
1012
1013
disk_changed->hide();
1014
}
1015
1016
void TextShaderEditor::trim_trailing_whitespace() {
1017
code_editor->trim_trailing_whitespace();
1018
}
1019
1020
void TextShaderEditor::trim_final_newlines() {
1021
code_editor->trim_final_newlines();
1022
}
1023
1024
void TextShaderEditor::set_toggle_list_control(Control *p_toggle_list_control) {
1025
code_editor->set_toggle_list_control(p_toggle_list_control);
1026
}
1027
1028
void TextShaderEditor::update_toggle_files_button() {
1029
code_editor->update_toggle_files_button();
1030
}
1031
1032
void TextShaderEditor::validate_script() {
1033
code_editor->_validate_script();
1034
}
1035
1036
bool TextShaderEditor::is_unsaved() const {
1037
return code_editor->get_text_editor()->get_saved_version() != code_editor->get_text_editor()->get_version();
1038
}
1039
1040
void TextShaderEditor::tag_saved_version() {
1041
code_editor->get_text_editor()->tag_saved_version();
1042
}
1043
1044
void TextShaderEditor::apply_shaders() {
1045
String editor_code = code_editor->get_text_editor()->get_text();
1046
if (shader.is_valid()) {
1047
String shader_code = shader->get_code();
1048
if (shader_code != editor_code || dependencies_version != code_editor->get_dependencies_version()) {
1049
code_editor->set_block_shader_changed(true);
1050
shader->set_code(editor_code);
1051
code_editor->set_block_shader_changed(false);
1052
shader->set_edited(true);
1053
}
1054
}
1055
if (shader_inc.is_valid()) {
1056
String shader_inc_code = shader_inc->get_code();
1057
if (shader_inc_code != editor_code || dependencies_version != code_editor->get_dependencies_version()) {
1058
code_editor->set_block_shader_changed(true);
1059
shader_inc->set_code(editor_code);
1060
code_editor->set_block_shader_changed(false);
1061
shader_inc->set_edited(true);
1062
}
1063
}
1064
1065
dependencies_version = code_editor->get_dependencies_version();
1066
}
1067
1068
void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
1069
Ref<InputEventMouseButton> mb = ev;
1070
1071
if (mb.is_valid()) {
1072
if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
1073
CodeEdit *tx = code_editor->get_text_editor();
1074
1075
tx->apply_ime();
1076
1077
Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
1078
int row = pos.y;
1079
int col = pos.x;
1080
tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click"));
1081
1082
if (tx->is_move_caret_on_right_click_enabled()) {
1083
tx->remove_secondary_carets();
1084
if (tx->has_selection()) {
1085
int from_line = tx->get_selection_from_line();
1086
int to_line = tx->get_selection_to_line();
1087
int from_column = tx->get_selection_from_column();
1088
int to_column = tx->get_selection_to_column();
1089
1090
if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) {
1091
// Right click is outside the selected text
1092
tx->deselect();
1093
}
1094
}
1095
if (!tx->has_selection()) {
1096
tx->set_caret_line(row, true, false, -1);
1097
tx->set_caret_column(col);
1098
}
1099
}
1100
_make_context_menu(tx->has_selection(), get_local_mouse_position());
1101
}
1102
}
1103
1104
Ref<InputEventKey> k = ev;
1105
if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) {
1106
CodeEdit *tx = code_editor->get_text_editor();
1107
tx->adjust_viewport_to_caret();
1108
_make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos()));
1109
context_menu->grab_focus();
1110
}
1111
}
1112
1113
void TextShaderEditor::_update_bookmark_list() {
1114
bookmarks_menu->clear();
1115
1116
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
1117
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL);
1118
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT);
1119
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV);
1120
1121
PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines();
1122
if (bookmark_list.is_empty()) {
1123
return;
1124
}
1125
1126
bookmarks_menu->add_separator();
1127
1128
for (int i = 0; i < bookmark_list.size(); i++) {
1129
String line = code_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges();
1130
// Limit the size of the line if too big.
1131
if (line.length() > 50) {
1132
line = line.substr(0, 50);
1133
}
1134
1135
bookmarks_menu->add_item(String::num_int64(bookmark_list[i] + 1) + " - \"" + line + "\"");
1136
bookmarks_menu->set_item_metadata(-1, bookmark_list[i]);
1137
}
1138
}
1139
1140
void TextShaderEditor::_bookmark_item_pressed(int p_idx) {
1141
if (p_idx < 4) { // Any item before the separator.
1142
_menu_option(bookmarks_menu->get_item_id(p_idx));
1143
} else {
1144
code_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx));
1145
}
1146
}
1147
1148
void TextShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
1149
context_menu->clear();
1150
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EMOJI_AND_SYMBOL_PICKER)) {
1151
context_menu->add_item(TTR("Emoji & Symbols"), EDIT_EMOJI_AND_SYMBOL);
1152
context_menu->add_separator();
1153
}
1154
if (p_selection) {
1155
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
1156
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
1157
}
1158
1159
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
1160
context_menu->add_separator();
1161
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
1162
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
1163
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
1164
1165
context_menu->add_separator();
1166
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
1167
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
1168
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
1169
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
1170
1171
context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !code_editor->get_text_editor()->has_undo());
1172
context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !code_editor->get_text_editor()->has_redo());
1173
1174
context_menu->set_position(get_screen_position() + p_position);
1175
context_menu->reset_size();
1176
context_menu->popup();
1177
}
1178
1179
TextShaderEditor::TextShaderEditor() {
1180
_update_warnings(false);
1181
1182
code_editor = memnew(ShaderTextEditor);
1183
1184
code_editor->connect("script_validated", callable_mp(this, &TextShaderEditor::_script_validated));
1185
1186
code_editor->set_v_size_flags(SIZE_EXPAND_FILL);
1187
code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1188
1189
code_editor->connect("show_warnings_panel", callable_mp(this, &TextShaderEditor::_show_warnings_panel));
1190
code_editor->connect(CoreStringName(script_changed), callable_mp(this, &TextShaderEditor::apply_shaders));
1191
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextShaderEditor::_project_settings_changed));
1192
1193
code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
1194
code_editor->get_text_editor()->set_context_menu_enabled(false);
1195
code_editor->get_text_editor()->set_draw_breakpoints_gutter(false);
1196
code_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
1197
code_editor->get_text_editor()->connect(SceneStringName(gui_input), callable_mp(this, &TextShaderEditor::_text_edit_gui_input));
1198
1199
code_editor->update_editor_settings();
1200
1201
context_menu = memnew(PopupMenu);
1202
add_child(context_menu);
1203
context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &TextShaderEditor::_menu_option));
1204
1205
VBoxContainer *main_container = memnew(VBoxContainer);
1206
main_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1207
menu_bar_hbox = memnew(HBoxContainer);
1208
1209
edit_menu = memnew(MenuButton);
1210
edit_menu->set_flat(false);
1211
edit_menu->set_theme_type_variation("FlatMenuButton");
1212
edit_menu->set_shortcut_context(this);
1213
edit_menu->set_text(TTR("Edit"));
1214
edit_menu->set_switch_on_hover(true);
1215
edit_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_prepare_edit_menu));
1216
1217
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
1218
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
1219
edit_menu->get_popup()->add_separator();
1220
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
1221
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
1222
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
1223
edit_menu->get_popup()->add_separator();
1224
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
1225
edit_menu->get_popup()->add_separator();
1226
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP);
1227
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN);
1228
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
1229
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
1230
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
1231
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
1232
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
1233
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
1234
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
1235
edit_menu->get_popup()->add_separator();
1236
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
1237
edit_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextShaderEditor::_menu_option));
1238
1239
search_menu = memnew(MenuButton);
1240
search_menu->set_flat(false);
1241
search_menu->set_theme_type_variation("FlatMenuButton");
1242
search_menu->set_shortcut_context(this);
1243
search_menu->set_text(TTR("Search"));
1244
search_menu->set_switch_on_hover(true);
1245
1246
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
1247
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
1248
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
1249
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
1250
search_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextShaderEditor::_menu_option));
1251
1252
MenuButton *goto_menu = memnew(MenuButton);
1253
goto_menu->set_flat(false);
1254
goto_menu->set_theme_type_variation("FlatMenuButton");
1255
goto_menu->set_shortcut_context(this);
1256
goto_menu->set_text(TTR("Go To"));
1257
goto_menu->set_switch_on_hover(true);
1258
goto_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextShaderEditor::_menu_option));
1259
1260
goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
1261
goto_menu->get_popup()->add_separator();
1262
1263
bookmarks_menu = memnew(PopupMenu);
1264
goto_menu->get_popup()->add_submenu_node_item(TTR("Bookmarks"), bookmarks_menu);
1265
_update_bookmark_list();
1266
bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_update_bookmark_list));
1267
bookmarks_menu->connect("index_pressed", callable_mp(this, &TextShaderEditor::_bookmark_item_pressed));
1268
1269
add_child(main_container);
1270
main_container->add_child(menu_bar_hbox);
1271
menu_bar_hbox->add_child(edit_menu);
1272
menu_bar_hbox->add_child(search_menu);
1273
menu_bar_hbox->add_child(goto_menu);
1274
menu_bar_hbox->add_spacer();
1275
1276
site_search = memnew(Button);
1277
site_search->set_theme_type_variation(SceneStringName(FlatButton));
1278
site_search->connect(SceneStringName(pressed), callable_mp(this, &TextShaderEditor::_menu_option).bind(HELP_DOCS));
1279
site_search->set_text(TTR("Online Docs"));
1280
site_search->set_tooltip_text(TTR("Open Godot online documentation."));
1281
menu_bar_hbox->add_child(site_search);
1282
1283
menu_bar_hbox->add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("ScriptEditorPanel"), EditorStringName(EditorStyles)));
1284
1285
VSplitContainer *editor_box = memnew(VSplitContainer);
1286
main_container->add_child(editor_box);
1287
editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1288
editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
1289
editor_box->add_child(code_editor);
1290
1291
FindReplaceBar *bar = memnew(FindReplaceBar);
1292
main_container->add_child(bar);
1293
bar->hide();
1294
code_editor->set_find_replace_bar(bar);
1295
1296
warnings_panel = memnew(RichTextLabel);
1297
warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
1298
warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
1299
warnings_panel->set_meta_underline(true);
1300
warnings_panel->set_selection_enabled(true);
1301
warnings_panel->set_context_menu_enabled(true);
1302
warnings_panel->set_focus_mode(FOCUS_CLICK);
1303
warnings_panel->hide();
1304
warnings_panel->connect("meta_clicked", callable_mp(this, &TextShaderEditor::_warning_clicked));
1305
editor_box->add_child(warnings_panel);
1306
code_editor->set_warnings_panel(warnings_panel);
1307
1308
goto_line_popup = memnew(GotoLinePopup);
1309
add_child(goto_line_popup);
1310
1311
disk_changed = memnew(ConfirmationDialog);
1312
1313
VBoxContainer *vbc = memnew(VBoxContainer);
1314
disk_changed->add_child(vbc);
1315
1316
Label *dl = memnew(Label);
1317
dl->set_focus_mode(FOCUS_ACCESSIBILITY);
1318
dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?"));
1319
vbc->add_child(dl);
1320
1321
disk_changed->connect(SceneStringName(confirmed), callable_mp(this, &TextShaderEditor::_reload));
1322
disk_changed->set_ok_button_text(TTR("Reload"));
1323
1324
disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
1325
disk_changed->connect("custom_action", callable_mp(this, &TextShaderEditor::save_external_data));
1326
1327
add_child(disk_changed);
1328
1329
_apply_editor_settings();
1330
code_editor->show_toggle_files_button(); // TODO: Disabled for now, because it doesn't work properly.
1331
}
1332
1333