diff options
71 files changed, 2071 insertions, 1724 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bd4eb906c0..7ac048b24a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ SCsub @godotengine/buildsystem /core/ @godotengine/core /core/crypto/ @godotengine/network /core/debugger/ @godotengine/debugger +/core/extension/ @godotengine/gdextension /core/input/ @godotengine/input # Doc @@ -110,10 +111,9 @@ doc_classes/* @godotengine/documentation /modules/xatlas_unwrap/ @godotengine/rendering ## Scripting -/modules/gdnative/ @godotengine/gdnative /modules/gdscript/ @godotengine/gdscript /modules/jsonrpc/ @godotengine/gdscript -/modules/mono/ @godotengine/mono +/modules/mono/ @godotengine/dotnet ## Text /modules/freetype/ @godotengine/buildsystem diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 5b4de06e9e..d8951ddb78 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -27,7 +27,7 @@ jobs: sudo apt-get install -qq dos2unix recode clang-format-13 libxml2-utils python3-pip moreutils sudo update-alternatives --remove-all clang-format || true sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-13 100 - sudo pip3 install black==22.3.0 pygments pytest + sudo pip3 install black==22.3.0 pygments pytest==7.1.2 mypy==0.971 - name: File formatting checks (file_format.sh) run: | @@ -41,6 +41,10 @@ jobs: run: | bash ./misc/scripts/black_format.sh + - name: Python scripts static analysis (mypy_check.sh) + run: | + bash ./misc/scripts/mypy_check.sh + - name: Python builders checks via pytest (pytest_builders.sh) run: | bash ./misc/scripts/pytest_builders.sh @@ -73,5 +73,4 @@ for more information. [](https://www.codetriage.com/godotengine/godot) [](https://hosted.weblate.org/engage/godot-engine/?utm_source=widget) -[](https://lgtm.com/projects/g/godotengine/godot/alerts) [](https://www.tickgit.com/browse?repo=github.com/godotengine/godot) diff --git a/SConstruct b/SConstruct index c86422c4e4..646f41839d 100644 --- a/SConstruct +++ b/SConstruct @@ -518,7 +518,7 @@ if selected_platform in platform_list: # are actually handled to change compile options, etc. detect.configure(env) - print(f'Building for platform "{selected_platform}", architecture "{env["arch"]}", target "{env["target"]}.') + print(f'Building for platform "{selected_platform}", architecture "{env["arch"]}", target "{env["target"]}".') if env.dev_build: print("NOTE: Developer build, with debug optimization level and debug symbols (unless overridden).") diff --git a/core/core_builders.py b/core/core_builders.py index b07daa80ae..b0a3b85d58 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -2,6 +2,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ +import zlib from platform_methods import subprocess_main @@ -33,7 +34,6 @@ def make_certs_header(target, source, env): g = open(dst, "w", encoding="utf-8") buf = f.read() decomp_size = len(buf) - import zlib # Use maximum zlib compression level to further reduce file size # (at the cost of initial build times). @@ -208,7 +208,7 @@ def make_license_header(target, source, env): from collections import OrderedDict - projects = OrderedDict() + projects: dict = OrderedDict() license_list = [] with open(src_copyright, "r", encoding="utf-8") as copyright_file: @@ -230,7 +230,7 @@ def make_license_header(target, source, env): part = {} reader.next_line() - data_list = [] + data_list: list = [] for project in iter(projects.values()): for part in project: part["file_index"] = len(data_list) diff --git a/core/input/input_builders.py b/core/input/input_builders.py index 16f125ff38..a7729c9af2 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -16,7 +16,7 @@ def make_default_controller_mappings(target, source, env): g.write('#include "core/input/default_controller_mappings.h"\n') # ensure mappings have a consistent order - platform_mappings = OrderedDict() + platform_mappings: dict = OrderedDict() for src_path in source: with open(src_path, "r") as f: # read mapping file and skip header diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index ad67cfa852..c30acf32bb 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -30,6 +30,8 @@ #include "a_star_grid_2d.h" +#include "core/variant/typed_array.h" + static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) { real_t dx = (real_t)ABS(p_to.x - p_from.x); real_t dy = (real_t)ABS(p_to.y - p_from.y); @@ -492,17 +494,17 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec return path; } -Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) { - ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method."); - ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height)); - ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height)); +TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) { + ERR_FAIL_COND_V_MSG(dirty, TypedArray<Vector2i>(), "Grid is not initialized. Call the update method."); + ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height)); + ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height)); Point *a = _get_point(p_from_id.x, p_from_id.y); Point *b = _get_point(p_to_id.x, p_to_id.y); if (a == b) { - Vector<Vector2> ret; - ret.push_back(Vector2((float)a->id.x, (float)a->id.y)); + TypedArray<Vector2i> ret; + ret.push_back(a); return ret; } @@ -511,7 +513,7 @@ Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector bool found_route = _solve(begin_point, end_point); if (!found_route) { - return Vector<Vector2>(); + return TypedArray<Vector2i>(); } Point *p = end_point; @@ -521,20 +523,18 @@ Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector p = p->prev_point; } - Vector<Vector2> path; + TypedArray<Vector2i> path; path.resize(pc); { - Vector2 *w = path.ptrw(); - p = end_point; int64_t idx = pc - 1; while (p != begin_point) { - w[idx--] = Vector2((float)p->id.x, (float)p->id.y); + path[idx--] = p->id; p = p->prev_point; } - w[0] = p->id; + path[0] = p->id; } return path; diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index bf6363aa01..1002f18738 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -169,7 +169,7 @@ public: void clear(); Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to); - Vector<Vector2> get_id_path(const Vector2i &p_from, const Vector2i &p_to); + TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to); }; VARIANT_ENUM_CAST(AStarGrid2D::DiagonalMode); diff --git a/core/math/bvh_abb.h b/core/math/bvh_abb.h index 8a44f1c4da..699f7de604 100644 --- a/core/math/bvh_abb.h +++ b/core/math/bvh_abb.h @@ -251,7 +251,9 @@ struct BVH_ABB { void expand(real_t p_change) { POINT change; - change.set_all(p_change); + for (int axis = 0; axis < POINT::AXIS_COUNT; ++axis) { + change[axis] = p_change; + } grow(change); } @@ -262,7 +264,9 @@ struct BVH_ABB { } void set_to_max_opposite_extents() { - neg_max.set_all(FLT_MAX); + for (int axis = 0; axis < POINT::AXIS_COUNT; ++axis) { + neg_max[axis] = FLT_MAX; + } min = neg_max; } diff --git a/core/math/vector2.h b/core/math/vector2.h index 9441f84087..75364f72f0 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -69,10 +69,6 @@ struct _NO_DISCARD_ Vector2 { return coord[p_idx]; } - _FORCE_INLINE_ void set_all(const real_t p_value) { - x = y = p_value; - } - _FORCE_INLINE_ Vector2::Axis min_axis_index() const { return x < y ? Vector2::AXIS_X : Vector2::AXIS_Y; } diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp index 4db45fe798..55ba509144 100644 --- a/core/math/vector3.cpp +++ b/core/math/vector3.cpp @@ -45,16 +45,6 @@ Vector3 Vector3::rotated(const Vector3 &p_axis, const real_t p_angle) const { return r; } -void Vector3::set_axis(const int p_axis, const real_t p_value) { - ERR_FAIL_INDEX(p_axis, 3); - coord[p_axis] = p_value; -} - -real_t Vector3::get_axis(const int p_axis) const { - ERR_FAIL_INDEX_V(p_axis, 3, 0); - return operator[](p_axis); -} - Vector3 Vector3::clamp(const Vector3 &p_min, const Vector3 &p_max) const { return Vector3( CLAMP(x, p_min.x, p_max.x), diff --git a/core/math/vector3.h b/core/math/vector3.h index 3944afa92e..62e810fb4d 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -68,13 +68,6 @@ struct _NO_DISCARD_ Vector3 { return coord[p_axis]; } - void set_axis(const int p_axis, const real_t p_value); - real_t get_axis(const int p_axis) const; - - _FORCE_INLINE_ void set_all(const real_t p_value) { - x = y = z = p_value; - } - _FORCE_INLINE_ Vector3::Axis min_axis_index() const { return x < y ? (x < z ? Vector3::AXIS_X : Vector3::AXIS_Z) : (y < z ? Vector3::AXIS_Y : Vector3::AXIS_Z); } diff --git a/core/math/vector3i.cpp b/core/math/vector3i.cpp index b8e74ea6d2..b248f35035 100644 --- a/core/math/vector3i.cpp +++ b/core/math/vector3i.cpp @@ -33,16 +33,6 @@ #include "core/math/vector3.h" #include "core/string/ustring.h" -void Vector3i::set_axis(const int p_axis, const int32_t p_value) { - ERR_FAIL_INDEX(p_axis, 3); - coord[p_axis] = p_value; -} - -int32_t Vector3i::get_axis(const int p_axis) const { - ERR_FAIL_INDEX_V(p_axis, 3, 0); - return operator[](p_axis); -} - Vector3i::Axis Vector3i::min_axis_index() const { return x < y ? (x < z ? Vector3i::AXIS_X : Vector3i::AXIS_Z) : (y < z ? Vector3i::AXIS_Y : Vector3i::AXIS_Z); } diff --git a/core/math/vector3i.h b/core/math/vector3i.h index c6d03cd031..710fd96376 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -66,9 +66,6 @@ struct _NO_DISCARD_ Vector3i { return coord[p_axis]; } - void set_axis(const int p_axis, const int32_t p_value); - int32_t get_axis(const int p_axis) const; - Vector3i::Axis min_axis_index() const; Vector3i::Axis max_axis_index() const; diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp index 3c25f454a3..55e51834df 100644 --- a/core/math/vector4.cpp +++ b/core/math/vector4.cpp @@ -33,16 +33,6 @@ #include "core/math/basis.h" #include "core/string/print_string.h" -void Vector4::set_axis(const int p_axis, const real_t p_value) { - ERR_FAIL_INDEX(p_axis, 4); - components[p_axis] = p_value; -} - -real_t Vector4::get_axis(const int p_axis) const { - ERR_FAIL_INDEX_V(p_axis, 4, 0); - return operator[](p_axis); -} - Vector4::Axis Vector4::min_axis_index() const { uint32_t min_index = 0; real_t min_value = x; diff --git a/core/math/vector4.h b/core/math/vector4.h index d89f3ddb05..426c473e13 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -65,11 +65,6 @@ struct _NO_DISCARD_ Vector4 { return components[p_axis]; } - _FORCE_INLINE_ void set_all(const real_t p_value); - - void set_axis(const int p_axis, const real_t p_value); - real_t get_axis(const int p_axis) const; - Vector4::Axis min_axis_index() const; Vector4::Axis max_axis_index() const; @@ -150,10 +145,6 @@ struct _NO_DISCARD_ Vector4 { } }; -void Vector4::set_all(const real_t p_value) { - x = y = z = p_value; -} - real_t Vector4::dot(const Vector4 &p_vec4) const { return x * p_vec4.x + y * p_vec4.y + z * p_vec4.z + w * p_vec4.w; } diff --git a/core/math/vector4i.cpp b/core/math/vector4i.cpp index a89b802675..77f6fbd5b7 100644 --- a/core/math/vector4i.cpp +++ b/core/math/vector4i.cpp @@ -33,16 +33,6 @@ #include "core/math/vector4.h" #include "core/string/ustring.h" -void Vector4i::set_axis(const int p_axis, const int32_t p_value) { - ERR_FAIL_INDEX(p_axis, 4); - coord[p_axis] = p_value; -} - -int32_t Vector4i::get_axis(const int p_axis) const { - ERR_FAIL_INDEX_V(p_axis, 4, 0); - return operator[](p_axis); -} - Vector4i::Axis Vector4i::min_axis_index() const { uint32_t min_index = 0; int32_t min_value = x; diff --git a/core/math/vector4i.h b/core/math/vector4i.h index fdf33b9569..a32414bb18 100644 --- a/core/math/vector4i.h +++ b/core/math/vector4i.h @@ -68,9 +68,6 @@ struct _NO_DISCARD_ Vector4i { return coord[p_axis]; } - void set_axis(const int p_axis, const int32_t p_value); - int32_t get_axis(const int p_axis) const; - Vector4i::Axis min_axis_index() const; Vector4i::Axis max_axis_index() const; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 75b797d397..6218c21cde 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4651,15 +4651,18 @@ String String::sprintf(const Array &values, bool *error) const { double value = values[value_index]; bool is_negative = (value < 0); String str = String::num(ABS(value), min_decimals); + bool not_numeric = isinf(value) || isnan(value); // Pad decimals out. - str = str.pad_decimals(min_decimals); + if (!not_numeric) { + str = str.pad_decimals(min_decimals); + } int initial_len = str.length(); // Padding. Leave room for sign later if required. int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; - String pad_char = pad_with_zeros ? String("0") : String(" "); + String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros if (left_justified) { str = str.rpad(pad_chars_count, pad_char); } else { @@ -4709,14 +4712,19 @@ String String::sprintf(const Array &values, bool *error) const { String str = "("; for (int i = 0; i < count; i++) { double val = vec[i]; + String number_str = String::num(ABS(val), min_decimals); + bool not_numeric = isinf(val) || isnan(val); + // Pad decimals out. - String number_str = String::num(ABS(val), min_decimals).pad_decimals(min_decimals); + if (!not_numeric) { + number_str = number_str.pad_decimals(min_decimals); + } int initial_len = number_str.length(); // Padding. Leave room for sign later if required. int pad_chars_count = val < 0 ? min_chars - 1 : min_chars; - String pad_char = pad_with_zeros ? String("0") : String(" "); + String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros if (left_justified) { number_str = number_str.rpad(pad_chars_count, pad_char); } else { diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml index 331862ebfa..8dde3748d7 100644 --- a/doc/classes/AStarGrid2D.xml +++ b/doc/classes/AStarGrid2D.xml @@ -53,7 +53,7 @@ </description> </method> <method name="get_id_path"> - <return type="PackedVector2Array" /> + <return type="Vector2i[]" /> <param index="0" name="from_id" type="Vector2i" /> <param index="1" name="to_id" type="Vector2i" /> <description> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index d62f704528..6d9b679fbc 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -83,7 +83,7 @@ <return type="Vector3" /> <param index="0" name="order" type="int" default="2" /> <description> - Returns the basis's rotation in the form of Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last). The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle). + Returns the basis's rotation in the form of Euler angles. The Euler order depends on the [param order] parameter, by default it uses the YXZ convention: when decomposing, first Z, then X, and Y last. The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle). Consider using the [method get_rotation_quaternion] method instead, which returns a [Quaternion] quaternion instead of Euler angles. </description> </method> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index a8569413ec..492a438d9b 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -526,7 +526,7 @@ def main() -> None: ) if os.path.exists(lang_file): try: - import polib + import polib # type: ignore except ImportError: print("Base template strings localization requires `polib`.") exit(1) @@ -739,9 +739,10 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(f"- {make_link(url, title)}\n\n") # Properties overview + ml: List[Tuple[Optional[str], ...]] = [] if len(class_def.properties) > 0: f.write(make_heading("Properties", "-")) - ml: List[Tuple[Optional[str], ...]] = [] + ml = [] for property_def in class_def.properties.values(): type_rst = property_def.type_name.to_rst(state) default = property_def.default_value @@ -757,7 +758,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Constructors, Methods, Operators overview if len(class_def.constructors) > 0: f.write(make_heading("Constructors", "-")) - ml: List[Tuple[Optional[str], ...]] = [] + ml = [] for method_list in class_def.constructors.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "constructor", state)) @@ -765,7 +766,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if len(class_def.methods) > 0: f.write(make_heading("Methods", "-")) - ml: List[Tuple[Optional[str], ...]] = [] + ml = [] for method_list in class_def.methods.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "method", state)) @@ -773,7 +774,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if len(class_def.operators) > 0: f.write(make_heading("Operators", "-")) - ml: List[Tuple[Optional[str], ...]] = [] + ml = [] for method_list in class_def.operators.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "operator", state)) @@ -858,7 +859,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(make_heading("Annotations", "-")) index = 0 - for method_list in class_def.annotations.values(): + for method_list in class_def.annotations.values(): # type: ignore for i, m in enumerate(method_list): if index != 0: f.write("----\n\n") @@ -1039,17 +1040,15 @@ def make_method_signature( ) -> Tuple[str, str]: ret_type = "" - is_method_def = isinstance(definition, MethodDef) - if is_method_def: + if isinstance(definition, MethodDef): ret_type = definition.return_type.to_rst(state) qualifiers = None - if is_method_def or isinstance(definition, AnnotationDef): + if isinstance(definition, (MethodDef, AnnotationDef)): qualifiers = definition.qualifiers out = "" - - if is_method_def and ref_type != "": + if isinstance(definition, MethodDef) and ref_type != "": if ref_type == "operator": op_name = definition.name.replace("<", "\\<") # So operator "<" gets correctly displayed. out += f":ref:`{op_name}<class_{class_def.name}_{ref_type}_{sanitize_operator_name(definition.name, state)}_{definition.return_type.type_name}>` " @@ -1456,18 +1455,14 @@ def format_text_block( escape_post = True elif cmd.startswith("param"): - valid_context = ( - isinstance(context, MethodDef) - or isinstance(context, SignalDef) - or isinstance(context, AnnotationDef) - ) + valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef)) if not valid_context: print_error( f'{state.current_class}.xml: Argument reference "{link_target}" used outside of method, signal, or annotation context in {context_name}.', state, ) else: - context_params: List[ParameterDef] = context.parameters + context_params: List[ParameterDef] = context.parameters # type: ignore found = False for param_def in context_params: if param_def.name == link_target: diff --git a/doc/translations/extract.py b/doc/translations/extract.py index 5708e0072d..ce645436d9 100644 --- a/doc/translations/extract.py +++ b/doc/translations/extract.py @@ -60,7 +60,7 @@ BASE_STRINGS = [ ## <xml-line-number-hack from="https://stackoverflow.com/a/36430270/10846399"> import sys -sys.modules["_elementtree"] = None +sys.modules["_elementtree"] = None # type: ignore import xml.etree.ElementTree as ET ## override the parser to get the line number diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 39617167ef..d106bb31c7 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -737,7 +737,6 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, RS::EnvironmentBG background = environment_get_background(p_env); if (sky) { - ERR_FAIL_COND(!sky); sky_material = sky->material; if (sky_material.is_valid()) { diff --git a/editor/editor_builders.py b/editor/editor_builders.py index e73fbc6107..696e3b64ec 100644 --- a/editor/editor_builders.py +++ b/editor/editor_builders.py @@ -9,6 +9,7 @@ import shutil import subprocess import tempfile import uuid +import zlib from platform_methods import subprocess_main @@ -28,7 +29,6 @@ def make_doc_header(target, source, env): buf = (docbegin + buf + docend).encode("utf-8") decomp_size = len(buf) - import zlib # Use maximum zlib compression level to further reduce file size # (at the cost of initial build times). @@ -88,9 +88,6 @@ def make_translations_header(target, source, env, category): g.write("#ifndef _{}_TRANSLATIONS_H\n".format(category.upper())) g.write("#define _{}_TRANSLATIONS_H\n".format(category.upper())) - import zlib - import os.path - sorted_paths = sorted(source, key=lambda path: os.path.splitext(os.path.basename(path))[0]) msgfmt_available = shutil.which("msgfmt") is not None diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 270f4560b7..65c65a517f 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -716,11 +716,11 @@ void EditorProperty::shortcut_input(const Ref<InputEvent> &p_event) { const Ref<InputEventKey> k = p_event; if (k.is_valid() && k->is_pressed()) { - if (ED_IS_SHORTCUT("property_editor/copy_property", p_event)) { - menu_option(MENU_COPY_PROPERTY); + if (ED_IS_SHORTCUT("property_editor/copy_value", p_event)) { + menu_option(MENU_COPY_VALUE); accept_event(); - } else if (ED_IS_SHORTCUT("property_editor/paste_property", p_event) && !is_read_only()) { - menu_option(MENU_PASTE_PROPERTY); + } else if (ED_IS_SHORTCUT("property_editor/paste_value", p_event) && !is_read_only()) { + menu_option(MENU_PASTE_VALUE); accept_event(); } else if (ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) { menu_option(MENU_COPY_PROPERTY_PATH); @@ -915,10 +915,10 @@ Control *EditorProperty::make_custom_tooltip(const String &p_text) const { void EditorProperty::menu_option(int p_option) { switch (p_option) { - case MENU_COPY_PROPERTY: { + case MENU_COPY_VALUE: { InspectorDock::get_inspector_singleton()->set_property_clipboard(object->get(property)); } break; - case MENU_PASTE_PROPERTY: { + case MENU_PASTE_VALUE: { emit_changed(property, InspectorDock::get_inspector_singleton()->get_property_clipboard()); } break; case MENU_COPY_PROPERTY_PATH: { @@ -1013,10 +1013,10 @@ void EditorProperty::_update_popup() { add_child(menu); menu->connect("id_pressed", callable_mp(this, &EditorProperty::menu_option)); } - menu->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/copy_property"), MENU_COPY_PROPERTY); - menu->add_icon_shortcut(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/paste_property"), MENU_PASTE_PROPERTY); + menu->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/copy_value"), MENU_COPY_VALUE); + menu->add_icon_shortcut(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/paste_value"), MENU_PASTE_VALUE); menu->add_icon_shortcut(get_theme_icon(SNAME("CopyNodePath"), SNAME("EditorIcons")), ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH); - menu->set_item_disabled(MENU_PASTE_PROPERTY, is_read_only()); + menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only()); if (!pin_hidden) { menu->add_separator(); if (can_pin) { @@ -4101,7 +4101,7 @@ EditorInspector::EditorInspector() { refresh_countdown = 0.33; } - ED_SHORTCUT("property_editor/copy_property", TTR("Copy Property"), KeyModifierMask::CMD_OR_CTRL | Key::C); - ED_SHORTCUT("property_editor/paste_property", TTR("Paste Property"), KeyModifierMask::CMD_OR_CTRL | Key::V); + ED_SHORTCUT("property_editor/copy_value", TTR("Copy Value"), KeyModifierMask::CMD_OR_CTRL | Key::C); + ED_SHORTCUT("property_editor/paste_value", TTR("Paste Value"), KeyModifierMask::CMD_OR_CTRL | Key::V); ED_SHORTCUT("property_editor/copy_property_path", TTR("Copy Property Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 872007e637..bada02e254 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -58,8 +58,8 @@ class EditorProperty : public Container { public: enum MenuItems { - MENU_COPY_PROPERTY, - MENU_PASTE_PROPERTY, + MENU_COPY_VALUE, + MENU_PASTE_VALUE, MENU_COPY_PROPERTY_PATH, MENU_PIN_VALUE, MENU_OPEN_DOCUMENTATION, diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 18561fe3a0..4dcd0573e6 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -49,6 +49,7 @@ #include "editor/find_in_files.h" #include "editor/node_dock.h" #include "editor/plugins/shader_editor_plugin.h" +#include "editor/plugins/text_shader_editor.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" #include "script_text_editor.h" @@ -1124,6 +1125,7 @@ TypedArray<Script> ScriptEditor::_get_open_scripts() const { bool ScriptEditor::toggle_scripts_panel() { list_split->set_visible(!list_split->is_visible()); + EditorSettings::get_singleton()->set_project_metadata("scripts_panel", "show_scripts_panel", list_split->is_visible()); return list_split->is_visible(); } @@ -3697,6 +3699,7 @@ ScriptEditor::ScriptEditor() { overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL); list_split->add_child(overview_vbox); + list_split->set_visible(EditorSettings::get_singleton()->get_project_metadata("scripts_panel", "show_scripts_panel", true)); buttons_hbox = memnew(HBoxContainer); overview_vbox->add_child(buttons_hbox); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 2eafa4fc91..de1a807721 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -30,1172 +30,12 @@ #include "shader_editor_plugin.h" -#include "core/io/resource_loader.h" -#include "core/io/resource_saver.h" -#include "core/os/keyboard.h" -#include "core/os/os.h" -#include "core/version_generated.gen.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" -#include "editor/editor_settings.h" #include "editor/filesystem_dock.h" +#include "editor/plugins/text_shader_editor.h" #include "editor/plugins/visual_shader_editor_plugin.h" -#include "editor/project_settings_editor.h" #include "editor/shader_create_dialog.h" -#include "scene/gui/split_container.h" -#include "servers/display_server.h" -#include "servers/rendering/shader_preprocessor.h" -#include "servers/rendering/shader_types.h" - -/*** SHADER SYNTAX HIGHLIGHTER ****/ - -Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { - Dictionary color_map; - - for (const Point2i ®ion : disabled_branch_regions) { - if (p_line >= region.x && p_line <= region.y) { - Dictionary highlighter_info; - highlighter_info["color"] = disabled_branch_color; - - color_map[0] = highlighter_info; - return color_map; - } - } - - return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line); -} - -void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) { - ERR_FAIL_COND(p_region.x < 0); - ERR_FAIL_COND(p_region.y < 0); - - for (int i = 0; i < disabled_branch_regions.size(); i++) { - ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists."); - } - - Point2i disabled_branch_region; - disabled_branch_region.x = p_region.x; - disabled_branch_region.y = p_region.y; - disabled_branch_regions.push_back(disabled_branch_region); - - clear_highlighting_cache(); -} - -void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() { - disabled_branch_regions.clear(); - clear_highlighting_cache(); -} - -void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) { - disabled_branch_color = p_color; - clear_highlighting_cache(); -} - -/*** SHADER SCRIPT EDITOR ****/ - -static bool saved_warnings_enabled = false; -static bool saved_treat_warning_as_errors = false; -static HashMap<ShaderWarning::Code, bool> saved_warnings; -static uint32_t saved_warning_flags = 0U; - -void ShaderTextEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - if (is_visible_in_tree()) { - _load_theme_settings(); - if (warnings.size() > 0 && last_compile_result == OK) { - warnings_panel->clear(); - _update_warning_panel(); - } - } - } break; - } -} - -Ref<Shader> ShaderTextEditor::get_edited_shader() const { - return shader; -} - -Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const { - return shader_inc; -} - -void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { - set_edited_shader(p_shader, p_shader->get_code()); -} - -void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) { - if (shader == p_shader) { - return; - } - if (shader.is_valid()) { - shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); - } - shader = p_shader; - shader_inc = Ref<ShaderInclude>(); - - set_edited_code(p_code); - - if (shader.is_valid()) { - shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); - } -} - -void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) { - set_edited_shader_include(p_shader_inc, p_shader_inc->get_code()); -} - -void ShaderTextEditor::_shader_changed() { - // This function is used for dependencies (include changing changes main shader and forces it to revalidate) - if (block_shader_changed) { - return; - } - dependencies_version++; - _validate_script(); -} - -void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) { - if (shader_inc == p_shader_inc) { - return; - } - if (shader_inc.is_valid()) { - shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); - } - shader_inc = p_shader_inc; - shader = Ref<Shader>(); - - set_edited_code(p_code); - - if (shader_inc.is_valid()) { - shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); - } -} - -void ShaderTextEditor::set_edited_code(const String &p_code) { - _load_theme_settings(); - - get_text_editor()->set_text(p_code); - get_text_editor()->clear_undo_history(); - get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0); - get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0); - get_text_editor()->tag_saved_version(); - - _validate_script(); - _line_col_changed(); -} - -void ShaderTextEditor::reload_text() { - ERR_FAIL_COND(shader.is_null()); - - CodeEdit *te = get_text_editor(); - int column = te->get_caret_column(); - int row = te->get_caret_line(); - int h = te->get_h_scroll(); - int v = te->get_v_scroll(); - - te->set_text(shader->get_code()); - te->set_caret_line(row); - te->set_caret_column(column); - te->set_h_scroll(h); - te->set_v_scroll(v); - - te->tag_saved_version(); - - update_line_and_column(); -} - -void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) { - warnings_panel = p_warnings_panel; -} - -void ShaderTextEditor::_load_theme_settings() { - CodeEdit *text_editor = get_text_editor(); - Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color"); - if (updated_marked_line_color != marked_line_color) { - for (int i = 0; i < text_editor->get_line_count(); i++) { - if (text_editor->get_line_background_color(i) == marked_line_color) { - text_editor->set_line_background_color(i, updated_marked_line_color); - } - } - marked_line_color = updated_marked_line_color; - } - - syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color")); - syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color")); - syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color")); - syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color")); - - syntax_highlighter->clear_keyword_colors(); - - const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); - const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); - - List<String> keywords; - ShaderLanguage::get_keyword_list(&keywords); - - for (const String &E : keywords) { - if (ShaderLanguage::is_control_flow_keyword(E)) { - syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); - } else { - syntax_highlighter->add_keyword_color(E, keyword_color); - } - } - - List<String> pp_keywords; - ShaderPreprocessor::get_keyword_list(&pp_keywords, false); - - for (const String &E : pp_keywords) { - syntax_highlighter->add_keyword_color(E, keyword_color); - } - - // Colorize built-ins like `COLOR` differently to make them easier - // to distinguish from keywords at a quick glance. - - List<String> built_ins; - - if (shader_inc.is_valid()) { - for (int i = 0; i < RenderingServer::SHADER_MAX; i++) { - for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) { - for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { - built_ins.push_back(F.key); - } - } - - const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i)); - - for (int j = 0; j < modes.size(); j++) { - const ShaderLanguage::ModeInfo &info = modes[j]; - - if (!info.options.is_empty()) { - for (int k = 0; k < info.options.size(); k++) { - built_ins.push_back(String(info.name) + "_" + String(info.options[k])); - } - } else { - built_ins.push_back(String(info.name)); - } - } - } - } else if (shader.is_valid()) { - for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) { - for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { - built_ins.push_back(F.key); - } - } - - const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - - for (int i = 0; i < modes.size(); i++) { - const ShaderLanguage::ModeInfo &info = modes[i]; - - if (!info.options.is_empty()) { - for (int j = 0; j < info.options.size(); j++) { - built_ins.push_back(String(info.name) + "_" + String(info.options[j])); - } - } else { - built_ins.push_back(String(info.name)); - } - } - } - - const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); - - for (const String &E : built_ins) { - syntax_highlighter->add_keyword_color(E, user_type_color); - } - - // Colorize comments. - const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); - syntax_highlighter->clear_color_regions(); - syntax_highlighter->add_color_region("/*", "*/", comment_color, false); - syntax_highlighter->add_color_region("//", "", comment_color, true); - syntax_highlighter->set_disabled_branch_color(comment_color); - - text_editor->clear_comment_delimiters(); - text_editor->add_comment_delimiter("/*", "*/", false); - text_editor->add_comment_delimiter("//", "", true); - - if (!text_editor->has_auto_brace_completion_open_key("/*")) { - text_editor->add_auto_brace_completion_pair("/*", "*/"); - } - - // Colorize preprocessor include strings. - const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); - syntax_highlighter->add_color_region("\"", "\"", string_color, false); - - if (warnings_panel) { - // Warnings panel. - warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); - warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); - } -} - -void ShaderTextEditor::_check_shader_mode() { - String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text()); - - Shader::Mode mode; - - if (type == "canvas_item") { - mode = Shader::MODE_CANVAS_ITEM; - } else if (type == "particles") { - mode = Shader::MODE_PARTICLES; - } else if (type == "sky") { - mode = Shader::MODE_SKY; - } else if (type == "fog") { - mode = Shader::MODE_FOG; - } else { - mode = Shader::MODE_SPATIAL; - } - - if (shader->get_mode() != mode) { - set_block_shader_changed(true); - shader->set_code(get_text_editor()->get_text()); - set_block_shader_changed(false); - _load_theme_settings(); - } -} - -static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { - RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable); - return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt); -} - -static String complete_from_path; - -static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) { - if (!p_efsd) { - return; - } - for (int i = 0; i < p_efsd->get_file_count(); i++) { - if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) { - String path = p_efsd->get_file_path(i); - if (path.begins_with(complete_from_path)) { - path = path.replace_first(complete_from_path, ""); - } - r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH)); - } - } - for (int j = 0; j < p_efsd->get_subdir_count(); j++) { - _complete_include_paths_search(p_efsd->get_subdir(j), r_options); - } -} - -static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) { - _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options); -} - -void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) { - List<ScriptLanguage::CodeCompletionOption> pp_options; - List<ScriptLanguage::CodeCompletionOption> pp_defines; - ShaderPreprocessor preprocessor; - String code; - complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir(); - if (!complete_from_path.ends_with("/")) { - complete_from_path += "/"; - } - preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths); - complete_from_path = String(); - if (pp_options.size()) { - for (const ScriptLanguage::CodeCompletionOption &E : pp_options) { - r_options->push_back(E); - } - return; - } - for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) { - r_options->push_back(E); - } - - ShaderLanguage sl; - String calltip; - ShaderLanguage::ShaderCompileInfo info; - info.global_shader_uniform_type_func = _get_global_shader_uniform_type; - - if (shader.is_null()) { - info.is_include = true; - - sl.complete(code, info, r_options, calltip); - get_text_editor()->set_code_hint(calltip); - return; - } - _check_shader_mode(); - info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); - info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - info.shader_types = ShaderTypes::get_singleton()->get_types(); - - sl.complete(code, info, r_options, calltip); - get_text_editor()->set_code_hint(calltip); -} - -void ShaderTextEditor::_validate_script() { - emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied - - String code; - - if (shader.is_valid()) { - _check_shader_mode(); - code = shader->get_code(); - } else { - code = shader_inc->get_code(); - } - - ShaderPreprocessor preprocessor; - String code_pp; - String error_pp; - List<ShaderPreprocessor::FilePosition> err_positions; - List<ShaderPreprocessor::Region> regions; - String filename; - if (shader.is_valid()) { - filename = shader->get_path(); - } else if (shader_inc.is_valid()) { - filename = shader_inc->get_path(); - } - last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, ®ions); - - for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); - } - - syntax_highlighter->clear_disabled_branch_regions(); - for (const ShaderPreprocessor::Region ®ion : regions) { - if (!region.enabled) { - if (filename != region.file) { - continue; - } - syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line)); - } - } - - set_error(""); - set_error_count(0); - - if (last_compile_result != OK) { - //preprocessor error - ERR_FAIL_COND(err_positions.size() == 0); - - String error_text = error_pp; - int error_line = err_positions.front()->get().line; - if (err_positions.size() == 1) { - // Error in main file - error_text = "error(" + itos(error_line) + "): " + error_text; - } else { - error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text; - set_error_count(err_positions.size() - 1); - } - - set_error(error_text); - set_error_pos(error_line - 1, 0); - for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); - } - get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); - - set_warning_count(0); - - } else { - ShaderLanguage sl; - - sl.enable_warning_checking(saved_warnings_enabled); - uint32_t flags = saved_warning_flags; - if (shader.is_null()) { - if (flags & ShaderWarning::UNUSED_CONSTANT) { - flags &= ~(ShaderWarning::UNUSED_CONSTANT); - } - if (flags & ShaderWarning::UNUSED_FUNCTION) { - flags &= ~(ShaderWarning::UNUSED_FUNCTION); - } - if (flags & ShaderWarning::UNUSED_STRUCT) { - flags &= ~(ShaderWarning::UNUSED_STRUCT); - } - if (flags & ShaderWarning::UNUSED_UNIFORM) { - flags &= ~(ShaderWarning::UNUSED_UNIFORM); - } - if (flags & ShaderWarning::UNUSED_VARYING) { - flags &= ~(ShaderWarning::UNUSED_VARYING); - } - } - sl.set_warning_flags(flags); - - ShaderLanguage::ShaderCompileInfo info; - info.global_shader_uniform_type_func = _get_global_shader_uniform_type; - - if (shader.is_null()) { - info.is_include = true; - } else { - Shader::Mode mode = shader->get_mode(); - info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode)); - info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode)); - info.shader_types = ShaderTypes::get_singleton()->get_types(); - } - - code = code_pp; - //compiler error - last_compile_result = sl.compile(code, info); - - if (last_compile_result != OK) { - String error_text; - int error_line; - Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions(); - if (include_positions.size() > 1) { - //error is in an include - error_line = include_positions[0].line; - error_text = "error(" + itos(error_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text(); - set_error_count(include_positions.size() - 1); - } else { - error_line = sl.get_error_line(); - error_text = "error(" + itos(error_line) + "): " + sl.get_error_text(); - set_error_count(0); - } - set_error(error_text); - set_error_pos(error_line - 1, 0); - get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); - } else { - set_error(""); - } - - if (warnings.size() > 0 || last_compile_result != OK) { - warnings_panel->clear(); - } - warnings.clear(); - for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { - warnings.push_back(E->get()); - } - if (warnings.size() > 0 && last_compile_result == OK) { - warnings.sort_custom<WarningsComparator>(); - _update_warning_panel(); - } else { - set_warning_count(0); - } - } - - emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts -} - -void ShaderTextEditor::_update_warning_panel() { - int warning_count = 0; - - warnings_panel->push_table(2); - for (int i = 0; i < warnings.size(); i++) { - ShaderWarning &w = warnings[i]; - - if (warning_count == 0) { - if (saved_treat_warning_as_errors) { - String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors."); - set_error_pos(w.get_line() - 1, 0); - set_error(error_text); - get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color); - } - } - - warning_count++; - int line = w.get_line(); - - // First cell. - warnings_panel->push_cell(); - warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - if (line != -1) { - warnings_panel->push_meta(line - 1); - warnings_panel->add_text(TTR("Line") + " " + itos(line)); - warnings_panel->add_text(" (" + w.get_name() + "):"); - warnings_panel->pop(); // Meta goto. - } else { - warnings_panel->add_text(w.get_name() + ":"); - } - warnings_panel->pop(); // Color. - warnings_panel->pop(); // Cell. - - // Second cell. - warnings_panel->push_cell(); - warnings_panel->add_text(w.get_message()); - warnings_panel->pop(); // Cell. - } - warnings_panel->pop(); // Table. - - set_warning_count(warning_count); -} - -void ShaderTextEditor::_bind_methods() { - ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid"))); -} - -ShaderTextEditor::ShaderTextEditor() { - syntax_highlighter.instantiate(); - get_text_editor()->set_syntax_highlighter(syntax_highlighter); -} - -/*** SCRIPT EDITOR ******/ - -void ShaderEditor::_menu_option(int p_option) { - switch (p_option) { - case EDIT_UNDO: { - shader_editor->get_text_editor()->undo(); - } break; - case EDIT_REDO: { - shader_editor->get_text_editor()->redo(); - } break; - case EDIT_CUT: { - shader_editor->get_text_editor()->cut(); - } break; - case EDIT_COPY: { - shader_editor->get_text_editor()->copy(); - } break; - case EDIT_PASTE: { - shader_editor->get_text_editor()->paste(); - } break; - case EDIT_SELECT_ALL: { - shader_editor->get_text_editor()->select_all(); - } break; - case EDIT_MOVE_LINE_UP: { - shader_editor->move_lines_up(); - } break; - case EDIT_MOVE_LINE_DOWN: { - shader_editor->move_lines_down(); - } break; - case EDIT_INDENT: { - if (shader.is_null()) { - return; - } - shader_editor->get_text_editor()->indent_lines(); - } break; - case EDIT_UNINDENT: { - if (shader.is_null()) { - return; - } - shader_editor->get_text_editor()->unindent_lines(); - } break; - case EDIT_DELETE_LINE: { - shader_editor->delete_lines(); - } break; - case EDIT_DUPLICATE_SELECTION: { - shader_editor->duplicate_selection(); - } break; - case EDIT_TOGGLE_COMMENT: { - if (shader.is_null()) { - return; - } - - shader_editor->toggle_inline_comment("//"); - - } break; - case EDIT_COMPLETE: { - shader_editor->get_text_editor()->request_code_completion(); - } break; - case SEARCH_FIND: { - shader_editor->get_find_replace_bar()->popup_search(); - } break; - case SEARCH_FIND_NEXT: { - shader_editor->get_find_replace_bar()->search_next(); - } break; - case SEARCH_FIND_PREV: { - shader_editor->get_find_replace_bar()->search_prev(); - } break; - case SEARCH_REPLACE: { - shader_editor->get_find_replace_bar()->popup_replace(); - } break; - case SEARCH_GOTO_LINE: { - goto_line_dialog->popup_find_line(shader_editor->get_text_editor()); - } break; - case BOOKMARK_TOGGLE: { - shader_editor->toggle_bookmark(); - } break; - case BOOKMARK_GOTO_NEXT: { - shader_editor->goto_next_bookmark(); - } break; - case BOOKMARK_GOTO_PREV: { - shader_editor->goto_prev_bookmark(); - } break; - case BOOKMARK_REMOVE_ALL: { - shader_editor->remove_all_bookmarks(); - } break; - case HELP_DOCS: { - OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL)); - } break; - } - if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { - shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus")); - } -} - -void ShaderEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - PopupMenu *popup = help_menu->get_popup(); - popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - } break; - - case NOTIFICATION_WM_WINDOW_FOCUS_IN: { - _check_for_external_edit(); - } break; - } -} - -void ShaderEditor::_editor_settings_changed() { - shader_editor->update_editor_settings(); - - shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EditorSettings::get_singleton()->get("text_editor/appearance/whitespace/line_spacing")); - shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false); - shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false); -} - -void ShaderEditor::_show_warnings_panel(bool p_show) { - warnings_panel->set_visible(p_show); -} - -void ShaderEditor::_warning_clicked(Variant p_line) { - if (p_line.get_type() == Variant::INT) { - shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); - } -} - -void ShaderEditor::_bind_methods() { - ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel); - ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked); - - ADD_SIGNAL(MethodInfo("validation_changed")); -} - -void ShaderEditor::ensure_select_current() { -} - -void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) { - shader_editor->goto_line_selection(p_line, p_begin, p_end); -} - -void ShaderEditor::_project_settings_changed() { - _update_warnings(true); -} - -void ShaderEditor::_update_warnings(bool p_validate) { - bool changed = false; - - bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize(); - if (warnings_enabled != saved_warnings_enabled) { - saved_warnings_enabled = warnings_enabled; - changed = true; - } - - bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize(); - if (treat_warning_as_errors != saved_treat_warning_as_errors) { - saved_treat_warning_as_errors = treat_warning_as_errors; - changed = true; - } - - bool update_flags = false; - - for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) { - ShaderWarning::Code code = (ShaderWarning::Code)i; - bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower()); - - if (saved_warnings[code] != value) { - saved_warnings[code] = value; - update_flags = true; - changed = true; - } - } - - if (update_flags) { - saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings); - } - - if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) { - shader_editor->validate_script(); - } -} - -void ShaderEditor::_check_for_external_edit() { - bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); - - if (shader_inc.is_valid()) { - if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) { - if (use_autoreload) { - _reload_shader_include_from_disk(); - } else { - disk_changed->call_deferred(SNAME("popup_centered")); - } - } - return; - } - - if (shader.is_null() || shader->is_built_in()) { - return; - } - - if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { - if (use_autoreload) { - _reload_shader_from_disk(); - } else { - disk_changed->call_deferred(SNAME("popup_centered")); - } - } -} - -void ShaderEditor::_reload_shader_from_disk() { - Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); - ERR_FAIL_COND(!rel_shader.is_valid()); - - shader_editor->set_block_shader_changed(true); - shader->set_code(rel_shader->get_code()); - shader_editor->set_block_shader_changed(false); - shader->set_last_modified_time(rel_shader->get_last_modified_time()); - shader_editor->reload_text(); -} - -void ShaderEditor::_reload_shader_include_from_disk() { - Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); - ERR_FAIL_COND(!rel_shader_include.is_valid()); - - shader_editor->set_block_shader_changed(true); - shader_inc->set_code(rel_shader_include->get_code()); - shader_editor->set_block_shader_changed(false); - shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time()); - shader_editor->reload_text(); -} - -void ShaderEditor::_reload() { - if (shader.is_valid()) { - _reload_shader_from_disk(); - } else if (shader_inc.is_valid()) { - _reload_shader_include_from_disk(); - } -} - -void ShaderEditor::edit(const Ref<Shader> &p_shader) { - if (p_shader.is_null() || !p_shader->is_text_shader()) { - return; - } - - if (shader == p_shader) { - return; - } - - shader = p_shader; - shader_inc = Ref<ShaderInclude>(); - - shader_editor->set_edited_shader(shader); -} - -void ShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) { - if (p_shader_inc.is_null()) { - return; - } - - if (shader_inc == p_shader_inc) { - return; - } - - shader_inc = p_shader_inc; - shader = Ref<Shader>(); - - shader_editor->set_edited_shader_include(p_shader_inc); -} - -void ShaderEditor::save_external_data(const String &p_str) { - if (shader.is_null() && shader_inc.is_null()) { - disk_changed->hide(); - return; - } - - apply_shaders(); - - Ref<Shader> edited_shader = shader_editor->get_edited_shader(); - if (edited_shader.is_valid()) { - ResourceSaver::save(edited_shader); - } - if (shader.is_valid() && shader != edited_shader) { - ResourceSaver::save(shader); - } - - Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include(); - if (edited_shader_inc.is_valid()) { - ResourceSaver::save(edited_shader_inc); - } - if (shader_inc.is_valid() && shader_inc != edited_shader_inc) { - ResourceSaver::save(shader_inc); - } - shader_editor->get_text_editor()->tag_saved_version(); - - disk_changed->hide(); -} - -void ShaderEditor::validate_script() { - shader_editor->_validate_script(); -} - -bool ShaderEditor::is_unsaved() const { - return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version(); -} - -void ShaderEditor::apply_shaders() { - String editor_code = shader_editor->get_text_editor()->get_text(); - if (shader.is_valid()) { - String shader_code = shader->get_code(); - if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { - shader_editor->set_block_shader_changed(true); - shader->set_code(editor_code); - shader_editor->set_block_shader_changed(false); - shader->set_edited(true); - } - } - if (shader_inc.is_valid()) { - String shader_inc_code = shader_inc->get_code(); - if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { - shader_editor->set_block_shader_changed(true); - shader_inc->set_code(editor_code); - shader_editor->set_block_shader_changed(false); - shader_inc->set_edited(true); - } - } - - dependencies_version = shader_editor->get_dependencies_version(); -} - -void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { - Ref<InputEventMouseButton> mb = ev; - - if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { - CodeEdit *tx = shader_editor->get_text_editor(); - - Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); - int row = pos.y; - int col = pos.x; - tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); - - if (tx->is_move_caret_on_right_click_enabled()) { - if (tx->has_selection()) { - int from_line = tx->get_selection_from_line(); - int to_line = tx->get_selection_to_line(); - int from_column = tx->get_selection_from_column(); - int to_column = tx->get_selection_to_column(); - - if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { - // Right click is outside the selected text - tx->deselect(); - } - } - if (!tx->has_selection()) { - tx->set_caret_line(row, true, false); - tx->set_caret_column(col); - } - } - _make_context_menu(tx->has_selection(), get_local_mouse_position()); - } - } - - Ref<InputEventKey> k = ev; - if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { - CodeEdit *tx = shader_editor->get_text_editor(); - tx->adjust_viewport_to_caret(); - _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); - context_menu->grab_focus(); - } -} - -void ShaderEditor::_update_bookmark_list() { - bookmarks_menu->clear(); - - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); - - PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines(); - if (bookmark_list.size() == 0) { - return; - } - - bookmarks_menu->add_separator(); - - for (int i = 0; i < bookmark_list.size(); i++) { - String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges(); - // Limit the size of the line if too big. - if (line.length() > 50) { - line = line.substr(0, 50); - } - - bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); - bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); - } -} - -void ShaderEditor::_bookmark_item_pressed(int p_idx) { - if (p_idx < 4) { // Any item before the separator. - _menu_option(bookmarks_menu->get_item_id(p_idx)); - } else { - shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); - } -} - -void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { - context_menu->clear(); - if (p_selection) { - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); - } - - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); - context_menu->add_separator(); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); - - context_menu->add_separator(); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - - context_menu->set_position(get_screen_position() + p_position); - context_menu->reset_size(); - context_menu->popup(); -} - -ShaderEditor::ShaderEditor() { - GLOBAL_DEF("debug/shader_language/warnings/enable", true); - GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false); - for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) { - GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true); - } - _update_warnings(false); - - shader_editor = memnew(ShaderTextEditor); - - shader_editor->connect("script_validated", callable_mp(this, &ShaderEditor::_script_validated)); - - shader_editor->set_v_size_flags(SIZE_EXPAND_FILL); - shader_editor->add_theme_constant_override("separation", 0); - shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - - shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel)); - shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders)); - EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed)); - ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed)); - - shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); - - shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); - shader_editor->get_text_editor()->set_context_menu_enabled(false); - shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &ShaderEditor::_text_edit_gui_input)); - - shader_editor->update_editor_settings(); - - context_menu = memnew(PopupMenu); - add_child(context_menu); - context_menu->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - VBoxContainer *main_container = memnew(VBoxContainer); - HBoxContainer *hbc = memnew(HBoxContainer); - - edit_menu = memnew(MenuButton); - edit_menu->set_shortcut_context(this); - edit_menu->set_text(TTR("Edit")); - edit_menu->set_switch_on_hover(true); - - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); - edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - search_menu = memnew(MenuButton); - search_menu->set_shortcut_context(this); - search_menu->set_text(TTR("Search")); - search_menu->set_switch_on_hover(true); - - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); - search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - MenuButton *goto_menu = memnew(MenuButton); - goto_menu->set_shortcut_context(this); - goto_menu->set_text(TTR("Go To")); - goto_menu->set_switch_on_hover(true); - goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); - goto_menu->get_popup()->add_separator(); - - bookmarks_menu = memnew(PopupMenu); - bookmarks_menu->set_name("Bookmarks"); - goto_menu->get_popup()->add_child(bookmarks_menu); - goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); - _update_bookmark_list(); - bookmarks_menu->connect("about_to_popup", callable_mp(this, &ShaderEditor::_update_bookmark_list)); - bookmarks_menu->connect("index_pressed", callable_mp(this, &ShaderEditor::_bookmark_item_pressed)); - - help_menu = memnew(MenuButton); - help_menu->set_text(TTR("Help")); - help_menu->set_switch_on_hover(true); - help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS); - help_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - add_child(main_container); - main_container->add_child(hbc); - hbc->add_child(search_menu); - hbc->add_child(edit_menu); - hbc->add_child(goto_menu); - hbc->add_child(help_menu); - hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); - - VSplitContainer *editor_box = memnew(VSplitContainer); - main_container->add_child(editor_box); - editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - editor_box->set_v_size_flags(SIZE_EXPAND_FILL); - editor_box->add_child(shader_editor); - - FindReplaceBar *bar = memnew(FindReplaceBar); - main_container->add_child(bar); - bar->hide(); - shader_editor->set_find_replace_bar(bar); - - warnings_panel = memnew(RichTextLabel); - warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); - warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); - warnings_panel->set_meta_underline(true); - warnings_panel->set_selection_enabled(true); - warnings_panel->set_focus_mode(FOCUS_CLICK); - warnings_panel->hide(); - warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked)); - editor_box->add_child(warnings_panel); - shader_editor->set_warnings_panel(warnings_panel); - - goto_line_dialog = memnew(GotoLineDialog); - add_child(goto_line_dialog); - - disk_changed = memnew(ConfirmationDialog); - - VBoxContainer *vbc = memnew(VBoxContainer); - disk_changed->add_child(vbc); - - Label *dl = memnew(Label); - dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?")); - vbc->add_child(dl); - - disk_changed->connect("confirmed", callable_mp(this, &ShaderEditor::_reload)); - disk_changed->set_ok_button_text(TTR("Reload")); - - disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); - disk_changed->connect("custom_action", callable_mp(this, &ShaderEditor::save_external_data)); - - add_child(disk_changed); - - _editor_settings_changed(); -} void ShaderEditorPlugin::_update_shader_list() { shader_list->clear(); @@ -1241,7 +81,7 @@ void ShaderEditorPlugin::_update_shader_list() { shader_list->select(shader_tabs->get_current_tab()); } - for (int i = 1; i < FILE_MAX; i++) { + for (int i = FILE_SAVE; i < FILE_MAX; i++) { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.size() == 0); } @@ -1250,7 +90,7 @@ void ShaderEditorPlugin::_update_shader_list() { void ShaderEditorPlugin::_update_shader_list_status() { for (int i = 0; i < shader_list->get_item_count(); i++) { - ShaderEditor *se = Object::cast_to<ShaderEditor>(shader_tabs->get_tab_control(i)); + TextShaderEditor *se = Object::cast_to<TextShaderEditor>(shader_tabs->get_tab_control(i)); if (se) { if (se->was_compilation_successful()) { shader_list->set_item_tag_icon(i, Ref<Texture2D>()); @@ -1285,7 +125,7 @@ void ShaderEditorPlugin::edit(Object *p_object) { } } es.shader_inc = Ref<ShaderInclude>(si); - es.shader_editor = memnew(ShaderEditor); + es.shader_editor = memnew(TextShaderEditor); es.shader_editor->edit(si); shader_tabs->add_child(es.shader_editor); es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list)); @@ -1305,7 +145,7 @@ void ShaderEditorPlugin::edit(Object *p_object) { shader_tabs->add_child(es.visual_shader_editor); es.visual_shader_editor->edit(vs.ptr()); } else { - es.shader_editor = memnew(ShaderEditor); + es.shader_editor = memnew(TextShaderEditor); shader_tabs->add_child(es.shader_editor); es.shader_editor->edit(s); es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list)); @@ -1330,7 +170,7 @@ void ShaderEditorPlugin::make_visible(bool p_visible) { void ShaderEditorPlugin::selected_notify() { } -ShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { +TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { for (uint32_t i = 0; i < edited_shaders.size(); i++) { if (edited_shaders[i].shader == p_for_shader) { return edited_shaders[i].shader_editor; @@ -1592,7 +432,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed)); file_hb->add_child(file_menu); - for (int i = 2; i < FILE_MAX; i++) { + for (int i = FILE_SAVE; i < FILE_MAX; i++) { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); } diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 5188639bfc..7638778af7 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -31,181 +31,14 @@ #ifndef SHADER_EDITOR_PLUGIN_H #define SHADER_EDITOR_PLUGIN_H -#include "editor/code_editor.h" #include "editor/editor_plugin.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/panel_container.h" -#include "scene/gui/rich_text_label.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/text_edit.h" -#include "scene/main/timer.h" -#include "scene/resources/shader.h" -#include "scene/resources/shader_include.h" -#include "servers/rendering/shader_warnings.h" -class ItemList; -class VisualShaderEditor; class HSplitContainer; +class ItemList; class ShaderCreateDialog; - -class GDShaderSyntaxHighlighter : public CodeHighlighter { - GDCLASS(GDShaderSyntaxHighlighter, CodeHighlighter) - -private: - Vector<Point2i> disabled_branch_regions; - Color disabled_branch_color; - -public: - virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; - - void add_disabled_branch_region(const Point2i &p_region); - void clear_disabled_branch_regions(); - void set_disabled_branch_color(const Color &p_color); -}; - -class ShaderTextEditor : public CodeTextEditor { - GDCLASS(ShaderTextEditor, CodeTextEditor); - - Color marked_line_color = Color(1, 1, 1); - - struct WarningsComparator { - _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); } - }; - - Ref<GDShaderSyntaxHighlighter> syntax_highlighter; - RichTextLabel *warnings_panel = nullptr; - Ref<Shader> shader; - Ref<ShaderInclude> shader_inc; - List<ShaderWarning> warnings; - Error last_compile_result = Error::OK; - - void _check_shader_mode(); - void _update_warning_panel(); - - bool block_shader_changed = false; - void _shader_changed(); - - uint32_t dependencies_version = 0; // Incremented if deps changed - -protected: - void _notification(int p_what); - static void _bind_methods(); - virtual void _load_theme_settings() override; - - virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override; - -public: - void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; } - uint32_t get_dependencies_version() const { return dependencies_version; } - - virtual void _validate_script() override; - - void reload_text(); - void set_warnings_panel(RichTextLabel *p_warnings_panel); - - Ref<Shader> get_edited_shader() const; - Ref<ShaderInclude> get_edited_shader_include() const; - - void set_edited_shader(const Ref<Shader> &p_shader); - void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code); - void set_edited_shader_include(const Ref<ShaderInclude> &p_include); - void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code); - void set_edited_code(const String &p_code); - - ShaderTextEditor(); -}; - -class ShaderEditor : public MarginContainer { - GDCLASS(ShaderEditor, MarginContainer); - - enum { - EDIT_UNDO, - EDIT_REDO, - EDIT_CUT, - EDIT_COPY, - EDIT_PASTE, - EDIT_SELECT_ALL, - EDIT_MOVE_LINE_UP, - EDIT_MOVE_LINE_DOWN, - EDIT_INDENT, - EDIT_UNINDENT, - EDIT_DELETE_LINE, - EDIT_DUPLICATE_SELECTION, - EDIT_TOGGLE_COMMENT, - EDIT_COMPLETE, - SEARCH_FIND, - SEARCH_FIND_NEXT, - SEARCH_FIND_PREV, - SEARCH_REPLACE, - SEARCH_GOTO_LINE, - BOOKMARK_TOGGLE, - BOOKMARK_GOTO_NEXT, - BOOKMARK_GOTO_PREV, - BOOKMARK_REMOVE_ALL, - HELP_DOCS, - }; - - MenuButton *edit_menu = nullptr; - MenuButton *search_menu = nullptr; - PopupMenu *bookmarks_menu = nullptr; - MenuButton *help_menu = nullptr; - PopupMenu *context_menu = nullptr; - RichTextLabel *warnings_panel = nullptr; - uint64_t idle = 0; - - GotoLineDialog *goto_line_dialog = nullptr; - ConfirmationDialog *erase_tab_confirm = nullptr; - ConfirmationDialog *disk_changed = nullptr; - - ShaderTextEditor *shader_editor = nullptr; - bool compilation_success = true; - - void _menu_option(int p_option); - mutable Ref<Shader> shader; - mutable Ref<ShaderInclude> shader_inc; - - void _editor_settings_changed(); - void _project_settings_changed(); - - void _check_for_external_edit(); - void _reload_shader_from_disk(); - void _reload_shader_include_from_disk(); - void _reload(); - void _show_warnings_panel(bool p_show); - void _warning_clicked(Variant p_line); - void _update_warnings(bool p_validate); - - void _script_validated(bool p_valid) { - compilation_success = p_valid; - emit_signal(SNAME("validation_changed")); - } - - uint32_t dependencies_version = 0xFFFFFFFF; - -protected: - void _notification(int p_what); - static void _bind_methods(); - void _make_context_menu(bool p_selection, Vector2 p_position); - void _text_edit_gui_input(const Ref<InputEvent> &p_ev); - - void _update_bookmark_list(); - void _bookmark_item_pressed(int p_idx); - -public: - bool was_compilation_successful() const { return compilation_success; } - void apply_shaders(); - void ensure_select_current(); - void edit(const Ref<Shader> &p_shader); - void edit(const Ref<ShaderInclude> &p_shader_inc); - void goto_line_selection(int p_line, int p_begin, int p_end); - void save_external_data(const String &p_str = ""); - void validate_script(); - bool is_unsaved() const; - - virtual Size2 get_minimum_size() const override { return Size2(0, 200); } - - ShaderEditor(); -}; +class TabContainer; +class TextShaderEditor; +class VisualShaderEditor; class ShaderEditorPlugin : public EditorPlugin { GDCLASS(ShaderEditorPlugin, EditorPlugin); @@ -213,12 +46,14 @@ class ShaderEditorPlugin : public EditorPlugin { struct EditedShader { Ref<Shader> shader; Ref<ShaderInclude> shader_inc; - ShaderEditor *shader_editor = nullptr; + TextShaderEditor *shader_editor = nullptr; VisualShaderEditor *visual_shader_editor = nullptr; }; LocalVector<EditedShader> edited_shaders; + // Always valid operations come first in the enum, file-specific ones + // should go after FILE_SAVE which is used to build the menu accordingly. enum { FILE_NEW, FILE_NEW_INCLUDE, @@ -265,7 +100,7 @@ public: virtual void make_visible(bool p_visible) override; virtual void selected_notify() override; - ShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader); + TextShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader); VisualShaderEditor *get_visual_shader_editor(const Ref<Shader> &p_for_shader); virtual void save_external_data() override; diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp new file mode 100644 index 0000000000..dfd5e76ba6 --- /dev/null +++ b/editor/plugins/text_shader_editor.cpp @@ -0,0 +1,1191 @@ +/*************************************************************************/ +/* text_shader_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "text_shader_editor.h" + +#include "core/version_generated.gen.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/filesystem_dock.h" +#include "editor/project_settings_editor.h" +#include "scene/gui/split_container.h" +#include "servers/rendering/shader_preprocessor.h" +#include "servers/rendering/shader_types.h" + +/*** SHADER SYNTAX HIGHLIGHTER ****/ + +Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { + Dictionary color_map; + + for (const Point2i ®ion : disabled_branch_regions) { + if (p_line >= region.x && p_line <= region.y) { + Dictionary highlighter_info; + highlighter_info["color"] = disabled_branch_color; + + color_map[0] = highlighter_info; + return color_map; + } + } + + return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line); +} + +void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) { + ERR_FAIL_COND(p_region.x < 0); + ERR_FAIL_COND(p_region.y < 0); + + for (int i = 0; i < disabled_branch_regions.size(); i++) { + ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists."); + } + + Point2i disabled_branch_region; + disabled_branch_region.x = p_region.x; + disabled_branch_region.y = p_region.y; + disabled_branch_regions.push_back(disabled_branch_region); + + clear_highlighting_cache(); +} + +void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() { + disabled_branch_regions.clear(); + clear_highlighting_cache(); +} + +void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) { + disabled_branch_color = p_color; + clear_highlighting_cache(); +} + +/*** SHADER SCRIPT EDITOR ****/ + +static bool saved_warnings_enabled = false; +static bool saved_treat_warning_as_errors = false; +static HashMap<ShaderWarning::Code, bool> saved_warnings; +static uint32_t saved_warning_flags = 0U; + +void ShaderTextEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + if (is_visible_in_tree()) { + _load_theme_settings(); + if (warnings.size() > 0 && last_compile_result == OK) { + warnings_panel->clear(); + _update_warning_panel(); + } + } + } break; + } +} + +Ref<Shader> ShaderTextEditor::get_edited_shader() const { + return shader; +} + +Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const { + return shader_inc; +} + +void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { + set_edited_shader(p_shader, p_shader->get_code()); +} + +void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) { + if (shader == p_shader) { + return; + } + if (shader.is_valid()) { + shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } + shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + set_edited_code(p_code); + + if (shader.is_valid()) { + shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) { + set_edited_shader_include(p_shader_inc, p_shader_inc->get_code()); +} + +void ShaderTextEditor::_shader_changed() { + // This function is used for dependencies (include changing changes main shader and forces it to revalidate) + if (block_shader_changed) { + return; + } + dependencies_version++; + _validate_script(); +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) { + if (shader_inc == p_shader_inc) { + return; + } + if (shader_inc.is_valid()) { + shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + set_edited_code(p_code); + + if (shader_inc.is_valid()) { + shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_code(const String &p_code) { + _load_theme_settings(); + + get_text_editor()->set_text(p_code); + get_text_editor()->clear_undo_history(); + get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0); + get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0); + get_text_editor()->tag_saved_version(); + + _validate_script(); + _line_col_changed(); +} + +void ShaderTextEditor::reload_text() { + ERR_FAIL_COND(shader.is_null()); + + CodeEdit *te = get_text_editor(); + int column = te->get_caret_column(); + int row = te->get_caret_line(); + int h = te->get_h_scroll(); + int v = te->get_v_scroll(); + + te->set_text(shader->get_code()); + te->set_caret_line(row); + te->set_caret_column(column); + te->set_h_scroll(h); + te->set_v_scroll(v); + + te->tag_saved_version(); + + update_line_and_column(); +} + +void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) { + warnings_panel = p_warnings_panel; +} + +void ShaderTextEditor::_load_theme_settings() { + CodeEdit *text_editor = get_text_editor(); + Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color"); + if (updated_marked_line_color != marked_line_color) { + for (int i = 0; i < text_editor->get_line_count(); i++) { + if (text_editor->get_line_background_color(i) == marked_line_color) { + text_editor->set_line_background_color(i, updated_marked_line_color); + } + } + marked_line_color = updated_marked_line_color; + } + + syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color")); + syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color")); + syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color")); + syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color")); + + syntax_highlighter->clear_keyword_colors(); + + const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); + + List<String> keywords; + ShaderLanguage::get_keyword_list(&keywords); + + for (const String &E : keywords) { + if (ShaderLanguage::is_control_flow_keyword(E)) { + syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); + } else { + syntax_highlighter->add_keyword_color(E, keyword_color); + } + } + + List<String> pp_keywords; + ShaderPreprocessor::get_keyword_list(&pp_keywords, false); + + for (const String &E : pp_keywords) { + syntax_highlighter->add_keyword_color(E, keyword_color); + } + + // Colorize built-ins like `COLOR` differently to make them easier + // to distinguish from keywords at a quick glance. + + List<String> built_ins; + + if (shader_inc.is_valid()) { + for (int i = 0; i < RenderingServer::SHADER_MAX; i++) { + for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) { + for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { + built_ins.push_back(F.key); + } + } + + const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i)); + + for (int j = 0; j < modes.size(); j++) { + const ShaderLanguage::ModeInfo &info = modes[j]; + + if (!info.options.is_empty()) { + for (int k = 0; k < info.options.size(); k++) { + built_ins.push_back(String(info.name) + "_" + String(info.options[k])); + } + } else { + built_ins.push_back(String(info.name)); + } + } + } + } else if (shader.is_valid()) { + for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) { + for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { + built_ins.push_back(F.key); + } + } + + const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); + + for (int i = 0; i < modes.size(); i++) { + const ShaderLanguage::ModeInfo &info = modes[i]; + + if (!info.options.is_empty()) { + for (int j = 0; j < info.options.size(); j++) { + built_ins.push_back(String(info.name) + "_" + String(info.options[j])); + } + } else { + built_ins.push_back(String(info.name)); + } + } + } + + const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); + + for (const String &E : built_ins) { + syntax_highlighter->add_keyword_color(E, user_type_color); + } + + // Colorize comments. + const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); + syntax_highlighter->clear_color_regions(); + syntax_highlighter->add_color_region("/*", "*/", comment_color, false); + syntax_highlighter->add_color_region("//", "", comment_color, true); + syntax_highlighter->set_disabled_branch_color(comment_color); + + text_editor->clear_comment_delimiters(); + text_editor->add_comment_delimiter("/*", "*/", false); + text_editor->add_comment_delimiter("//", "", true); + + if (!text_editor->has_auto_brace_completion_open_key("/*")) { + text_editor->add_auto_brace_completion_pair("/*", "*/"); + } + + // Colorize preprocessor include strings. + const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + syntax_highlighter->add_color_region("\"", "\"", string_color, false); + + if (warnings_panel) { + // Warnings panel. + warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); + warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); + } +} + +void ShaderTextEditor::_check_shader_mode() { + String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text()); + + Shader::Mode mode; + + if (type == "canvas_item") { + mode = Shader::MODE_CANVAS_ITEM; + } else if (type == "particles") { + mode = Shader::MODE_PARTICLES; + } else if (type == "sky") { + mode = Shader::MODE_SKY; + } else if (type == "fog") { + mode = Shader::MODE_FOG; + } else { + mode = Shader::MODE_SPATIAL; + } + + if (shader->get_mode() != mode) { + set_block_shader_changed(true); + shader->set_code(get_text_editor()->get_text()); + set_block_shader_changed(false); + _load_theme_settings(); + } +} + +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable); + return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt); +} + +static String complete_from_path; + +static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) { + if (!p_efsd) { + return; + } + for (int i = 0; i < p_efsd->get_file_count(); i++) { + if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) { + String path = p_efsd->get_file_path(i); + if (path.begins_with(complete_from_path)) { + path = path.replace_first(complete_from_path, ""); + } + r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH)); + } + } + for (int j = 0; j < p_efsd->get_subdir_count(); j++) { + _complete_include_paths_search(p_efsd->get_subdir(j), r_options); + } +} + +static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) { + _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options); +} + +void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) { + List<ScriptLanguage::CodeCompletionOption> pp_options; + List<ScriptLanguage::CodeCompletionOption> pp_defines; + ShaderPreprocessor preprocessor; + String code; + complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir(); + if (!complete_from_path.ends_with("/")) { + complete_from_path += "/"; + } + preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths); + complete_from_path = String(); + if (pp_options.size()) { + for (const ScriptLanguage::CodeCompletionOption &E : pp_options) { + r_options->push_back(E); + } + return; + } + for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) { + r_options->push_back(E); + } + + ShaderLanguage sl; + String calltip; + ShaderLanguage::ShaderCompileInfo info; + info.global_shader_uniform_type_func = _get_global_shader_uniform_type; + + if (shader.is_null()) { + info.is_include = true; + + sl.complete(code, info, r_options, calltip); + get_text_editor()->set_code_hint(calltip); + return; + } + _check_shader_mode(); + info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); + info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); + info.shader_types = ShaderTypes::get_singleton()->get_types(); + + sl.complete(code, info, r_options, calltip); + get_text_editor()->set_code_hint(calltip); +} + +void ShaderTextEditor::_validate_script() { + emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied + + String code; + + if (shader.is_valid()) { + _check_shader_mode(); + code = shader->get_code(); + } else { + code = shader_inc->get_code(); + } + + ShaderPreprocessor preprocessor; + String code_pp; + String error_pp; + List<ShaderPreprocessor::FilePosition> err_positions; + List<ShaderPreprocessor::Region> regions; + String filename; + if (shader.is_valid()) { + filename = shader->get_path(); + } else if (shader_inc.is_valid()) { + filename = shader_inc->get_path(); + } + last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, ®ions); + + for (int i = 0; i < get_text_editor()->get_line_count(); i++) { + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + } + + syntax_highlighter->clear_disabled_branch_regions(); + for (const ShaderPreprocessor::Region ®ion : regions) { + if (!region.enabled) { + if (filename != region.file) { + continue; + } + syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line)); + } + } + + set_error(""); + set_error_count(0); + + if (last_compile_result != OK) { + //preprocessor error + ERR_FAIL_COND(err_positions.size() == 0); + + String error_text = error_pp; + int error_line = err_positions.front()->get().line; + if (err_positions.size() == 1) { + // Error in main file + error_text = "error(" + itos(error_line) + "): " + error_text; + } else { + error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text; + set_error_count(err_positions.size() - 1); + } + + set_error(error_text); + set_error_pos(error_line - 1, 0); + for (int i = 0; i < get_text_editor()->get_line_count(); i++) { + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + } + get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); + + set_warning_count(0); + + } else { + ShaderLanguage sl; + + sl.enable_warning_checking(saved_warnings_enabled); + uint32_t flags = saved_warning_flags; + if (shader.is_null()) { + if (flags & ShaderWarning::UNUSED_CONSTANT) { + flags &= ~(ShaderWarning::UNUSED_CONSTANT); + } + if (flags & ShaderWarning::UNUSED_FUNCTION) { + flags &= ~(ShaderWarning::UNUSED_FUNCTION); + } + if (flags & ShaderWarning::UNUSED_STRUCT) { + flags &= ~(ShaderWarning::UNUSED_STRUCT); + } + if (flags & ShaderWarning::UNUSED_UNIFORM) { + flags &= ~(ShaderWarning::UNUSED_UNIFORM); + } + if (flags & ShaderWarning::UNUSED_VARYING) { + flags &= ~(ShaderWarning::UNUSED_VARYING); + } + } + sl.set_warning_flags(flags); + + ShaderLanguage::ShaderCompileInfo info; + info.global_shader_uniform_type_func = _get_global_shader_uniform_type; + + if (shader.is_null()) { + info.is_include = true; + } else { + Shader::Mode mode = shader->get_mode(); + info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode)); + info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode)); + info.shader_types = ShaderTypes::get_singleton()->get_types(); + } + + code = code_pp; + //compiler error + last_compile_result = sl.compile(code, info); + + if (last_compile_result != OK) { + String error_text; + int error_line; + Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions(); + if (include_positions.size() > 1) { + //error is in an include + error_line = include_positions[0].line; + error_text = "error(" + itos(error_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text(); + set_error_count(include_positions.size() - 1); + } else { + error_line = sl.get_error_line(); + error_text = "error(" + itos(error_line) + "): " + sl.get_error_text(); + set_error_count(0); + } + set_error(error_text); + set_error_pos(error_line - 1, 0); + get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); + } else { + set_error(""); + } + + if (warnings.size() > 0 || last_compile_result != OK) { + warnings_panel->clear(); + } + warnings.clear(); + for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { + warnings.push_back(E->get()); + } + if (warnings.size() > 0 && last_compile_result == OK) { + warnings.sort_custom<WarningsComparator>(); + _update_warning_panel(); + } else { + set_warning_count(0); + } + } + + emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts +} + +void ShaderTextEditor::_update_warning_panel() { + int warning_count = 0; + + warnings_panel->push_table(2); + for (int i = 0; i < warnings.size(); i++) { + ShaderWarning &w = warnings[i]; + + if (warning_count == 0) { + if (saved_treat_warning_as_errors) { + String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors."); + set_error_pos(w.get_line() - 1, 0); + set_error(error_text); + get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color); + } + } + + warning_count++; + int line = w.get_line(); + + // First cell. + warnings_panel->push_cell(); + warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + if (line != -1) { + warnings_panel->push_meta(line - 1); + warnings_panel->add_text(TTR("Line") + " " + itos(line)); + warnings_panel->add_text(" (" + w.get_name() + "):"); + warnings_panel->pop(); // Meta goto. + } else { + warnings_panel->add_text(w.get_name() + ":"); + } + warnings_panel->pop(); // Color. + warnings_panel->pop(); // Cell. + + // Second cell. + warnings_panel->push_cell(); + warnings_panel->add_text(w.get_message()); + warnings_panel->pop(); // Cell. + } + warnings_panel->pop(); // Table. + + set_warning_count(warning_count); +} + +void ShaderTextEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid"))); +} + +ShaderTextEditor::ShaderTextEditor() { + syntax_highlighter.instantiate(); + get_text_editor()->set_syntax_highlighter(syntax_highlighter); +} + +/*** SCRIPT EDITOR ******/ + +void TextShaderEditor::_menu_option(int p_option) { + switch (p_option) { + case EDIT_UNDO: { + shader_editor->get_text_editor()->undo(); + } break; + case EDIT_REDO: { + shader_editor->get_text_editor()->redo(); + } break; + case EDIT_CUT: { + shader_editor->get_text_editor()->cut(); + } break; + case EDIT_COPY: { + shader_editor->get_text_editor()->copy(); + } break; + case EDIT_PASTE: { + shader_editor->get_text_editor()->paste(); + } break; + case EDIT_SELECT_ALL: { + shader_editor->get_text_editor()->select_all(); + } break; + case EDIT_MOVE_LINE_UP: { + shader_editor->move_lines_up(); + } break; + case EDIT_MOVE_LINE_DOWN: { + shader_editor->move_lines_down(); + } break; + case EDIT_INDENT: { + if (shader.is_null()) { + return; + } + shader_editor->get_text_editor()->indent_lines(); + } break; + case EDIT_UNINDENT: { + if (shader.is_null()) { + return; + } + shader_editor->get_text_editor()->unindent_lines(); + } break; + case EDIT_DELETE_LINE: { + shader_editor->delete_lines(); + } break; + case EDIT_DUPLICATE_SELECTION: { + shader_editor->duplicate_selection(); + } break; + case EDIT_TOGGLE_COMMENT: { + if (shader.is_null()) { + return; + } + + shader_editor->toggle_inline_comment("//"); + + } break; + case EDIT_COMPLETE: { + shader_editor->get_text_editor()->request_code_completion(); + } break; + case SEARCH_FIND: { + shader_editor->get_find_replace_bar()->popup_search(); + } break; + case SEARCH_FIND_NEXT: { + shader_editor->get_find_replace_bar()->search_next(); + } break; + case SEARCH_FIND_PREV: { + shader_editor->get_find_replace_bar()->search_prev(); + } break; + case SEARCH_REPLACE: { + shader_editor->get_find_replace_bar()->popup_replace(); + } break; + case SEARCH_GOTO_LINE: { + goto_line_dialog->popup_find_line(shader_editor->get_text_editor()); + } break; + case BOOKMARK_TOGGLE: { + shader_editor->toggle_bookmark(); + } break; + case BOOKMARK_GOTO_NEXT: { + shader_editor->goto_next_bookmark(); + } break; + case BOOKMARK_GOTO_PREV: { + shader_editor->goto_prev_bookmark(); + } break; + case BOOKMARK_REMOVE_ALL: { + shader_editor->remove_all_bookmarks(); + } break; + case HELP_DOCS: { + OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL)); + } break; + } + if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { + shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus")); + } +} + +void TextShaderEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + PopupMenu *popup = help_menu->get_popup(); + popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + _check_for_external_edit(); + } break; + } +} + +void TextShaderEditor::_editor_settings_changed() { + shader_editor->update_editor_settings(); + + shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EditorSettings::get_singleton()->get("text_editor/appearance/whitespace/line_spacing")); + shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false); + shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false); +} + +void TextShaderEditor::_show_warnings_panel(bool p_show) { + warnings_panel->set_visible(p_show); +} + +void TextShaderEditor::_warning_clicked(Variant p_line) { + if (p_line.get_type() == Variant::INT) { + shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); + } +} + +void TextShaderEditor::_bind_methods() { + ClassDB::bind_method("_show_warnings_panel", &TextShaderEditor::_show_warnings_panel); + ClassDB::bind_method("_warning_clicked", &TextShaderEditor::_warning_clicked); + + ADD_SIGNAL(MethodInfo("validation_changed")); +} + +void TextShaderEditor::ensure_select_current() { +} + +void TextShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + shader_editor->goto_line_selection(p_line, p_begin, p_end); +} + +void TextShaderEditor::_project_settings_changed() { + _update_warnings(true); +} + +void TextShaderEditor::_update_warnings(bool p_validate) { + bool changed = false; + + bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize(); + if (warnings_enabled != saved_warnings_enabled) { + saved_warnings_enabled = warnings_enabled; + changed = true; + } + + bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize(); + if (treat_warning_as_errors != saved_treat_warning_as_errors) { + saved_treat_warning_as_errors = treat_warning_as_errors; + changed = true; + } + + bool update_flags = false; + + for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) { + ShaderWarning::Code code = (ShaderWarning::Code)i; + bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower()); + + if (saved_warnings[code] != value) { + saved_warnings[code] = value; + update_flags = true; + changed = true; + } + } + + if (update_flags) { + saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings); + } + + if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) { + shader_editor->validate_script(); + } +} + +void TextShaderEditor::_check_for_external_edit() { + bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); + + if (shader_inc.is_valid()) { + if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) { + if (use_autoreload) { + _reload_shader_include_from_disk(); + } else { + disk_changed->call_deferred(SNAME("popup_centered")); + } + } + return; + } + + if (shader.is_null() || shader->is_built_in()) { + return; + } + + if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { + if (use_autoreload) { + _reload_shader_from_disk(); + } else { + disk_changed->call_deferred(SNAME("popup_centered")); + } + } +} + +void TextShaderEditor::_reload_shader_from_disk() { + Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND(!rel_shader.is_valid()); + + shader_editor->set_block_shader_changed(true); + shader->set_code(rel_shader->get_code()); + shader_editor->set_block_shader_changed(false); + shader->set_last_modified_time(rel_shader->get_last_modified_time()); + shader_editor->reload_text(); +} + +void TextShaderEditor::_reload_shader_include_from_disk() { + Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND(!rel_shader_include.is_valid()); + + shader_editor->set_block_shader_changed(true); + shader_inc->set_code(rel_shader_include->get_code()); + shader_editor->set_block_shader_changed(false); + shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time()); + shader_editor->reload_text(); +} + +void TextShaderEditor::_reload() { + if (shader.is_valid()) { + _reload_shader_from_disk(); + } else if (shader_inc.is_valid()) { + _reload_shader_include_from_disk(); + } +} + +void TextShaderEditor::edit(const Ref<Shader> &p_shader) { + if (p_shader.is_null() || !p_shader->is_text_shader()) { + return; + } + + if (shader == p_shader) { + return; + } + + shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + shader_editor->set_edited_shader(shader); +} + +void TextShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) { + if (p_shader_inc.is_null()) { + return; + } + + if (shader_inc == p_shader_inc) { + return; + } + + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + shader_editor->set_edited_shader_include(p_shader_inc); +} + +void TextShaderEditor::save_external_data(const String &p_str) { + if (shader.is_null() && shader_inc.is_null()) { + disk_changed->hide(); + return; + } + + apply_shaders(); + + Ref<Shader> edited_shader = shader_editor->get_edited_shader(); + if (edited_shader.is_valid()) { + ResourceSaver::save(edited_shader); + } + if (shader.is_valid() && shader != edited_shader) { + ResourceSaver::save(shader); + } + + Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include(); + if (edited_shader_inc.is_valid()) { + ResourceSaver::save(edited_shader_inc); + } + if (shader_inc.is_valid() && shader_inc != edited_shader_inc) { + ResourceSaver::save(shader_inc); + } + shader_editor->get_text_editor()->tag_saved_version(); + + disk_changed->hide(); +} + +void TextShaderEditor::validate_script() { + shader_editor->_validate_script(); +} + +bool TextShaderEditor::is_unsaved() const { + return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version(); +} + +void TextShaderEditor::apply_shaders() { + String editor_code = shader_editor->get_text_editor()->get_text(); + if (shader.is_valid()) { + String shader_code = shader->get_code(); + if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { + shader_editor->set_block_shader_changed(true); + shader->set_code(editor_code); + shader_editor->set_block_shader_changed(false); + shader->set_edited(true); + } + } + if (shader_inc.is_valid()) { + String shader_inc_code = shader_inc->get_code(); + if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { + shader_editor->set_block_shader_changed(true); + shader_inc->set_code(editor_code); + shader_editor->set_block_shader_changed(false); + shader_inc->set_edited(true); + } + } + + dependencies_version = shader_editor->get_dependencies_version(); +} + +void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { + Ref<InputEventMouseButton> mb = ev; + + if (mb.is_valid()) { + if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { + CodeEdit *tx = shader_editor->get_text_editor(); + + Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); + int row = pos.y; + int col = pos.x; + tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); + + if (tx->is_move_caret_on_right_click_enabled()) { + if (tx->has_selection()) { + int from_line = tx->get_selection_from_line(); + int to_line = tx->get_selection_to_line(); + int from_column = tx->get_selection_from_column(); + int to_column = tx->get_selection_to_column(); + + if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { + // Right click is outside the selected text + tx->deselect(); + } + } + if (!tx->has_selection()) { + tx->set_caret_line(row, true, false); + tx->set_caret_column(col); + } + } + _make_context_menu(tx->has_selection(), get_local_mouse_position()); + } + } + + Ref<InputEventKey> k = ev; + if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { + CodeEdit *tx = shader_editor->get_text_editor(); + tx->adjust_viewport_to_caret(); + _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); + context_menu->grab_focus(); + } +} + +void TextShaderEditor::_update_bookmark_list() { + bookmarks_menu->clear(); + + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); + + PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines(); + if (bookmark_list.size() == 0) { + return; + } + + bookmarks_menu->add_separator(); + + for (int i = 0; i < bookmark_list.size(); i++) { + String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges(); + // Limit the size of the line if too big. + if (line.length() > 50) { + line = line.substr(0, 50); + } + + bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); + } +} + +void TextShaderEditor::_bookmark_item_pressed(int p_idx) { + if (p_idx < 4) { // Any item before the separator. + _menu_option(bookmarks_menu->get_item_id(p_idx)); + } else { + shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); + } +} + +void TextShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { + context_menu->clear(); + if (p_selection) { + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); + } + + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); + + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); + + context_menu->set_position(get_screen_position() + p_position); + context_menu->reset_size(); + context_menu->popup(); +} + +TextShaderEditor::TextShaderEditor() { + GLOBAL_DEF("debug/shader_language/warnings/enable", true); + GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false); + for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) { + GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true); + } + _update_warnings(false); + + shader_editor = memnew(ShaderTextEditor); + + shader_editor->connect("script_validated", callable_mp(this, &TextShaderEditor::_script_validated)); + + shader_editor->set_v_size_flags(SIZE_EXPAND_FILL); + shader_editor->add_theme_constant_override("separation", 0); + shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + + shader_editor->connect("show_warnings_panel", callable_mp(this, &TextShaderEditor::_show_warnings_panel)); + shader_editor->connect("script_changed", callable_mp(this, &TextShaderEditor::apply_shaders)); + EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextShaderEditor::_editor_settings_changed)); + ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &TextShaderEditor::_project_settings_changed)); + + shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); + + shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); + shader_editor->get_text_editor()->set_context_menu_enabled(false); + shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &TextShaderEditor::_text_edit_gui_input)); + + shader_editor->update_editor_settings(); + + context_menu = memnew(PopupMenu); + add_child(context_menu); + context_menu->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + VBoxContainer *main_container = memnew(VBoxContainer); + HBoxContainer *hbc = memnew(HBoxContainer); + + edit_menu = memnew(MenuButton); + edit_menu->set_shortcut_context(this); + edit_menu->set_text(TTR("Edit")); + edit_menu->set_switch_on_hover(true); + + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); + edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + search_menu = memnew(MenuButton); + search_menu->set_shortcut_context(this); + search_menu->set_text(TTR("Search")); + search_menu->set_switch_on_hover(true); + + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); + search_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + MenuButton *goto_menu = memnew(MenuButton); + goto_menu->set_shortcut_context(this); + goto_menu->set_text(TTR("Go To")); + goto_menu->set_switch_on_hover(true); + goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); + goto_menu->get_popup()->add_separator(); + + bookmarks_menu = memnew(PopupMenu); + bookmarks_menu->set_name("Bookmarks"); + goto_menu->get_popup()->add_child(bookmarks_menu); + goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); + _update_bookmark_list(); + bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_update_bookmark_list)); + bookmarks_menu->connect("index_pressed", callable_mp(this, &TextShaderEditor::_bookmark_item_pressed)); + + help_menu = memnew(MenuButton); + help_menu->set_text(TTR("Help")); + help_menu->set_switch_on_hover(true); + help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS); + help_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + add_child(main_container); + main_container->add_child(hbc); + hbc->add_child(search_menu); + hbc->add_child(edit_menu); + hbc->add_child(goto_menu); + hbc->add_child(help_menu); + hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + + VSplitContainer *editor_box = memnew(VSplitContainer); + main_container->add_child(editor_box); + editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + editor_box->set_v_size_flags(SIZE_EXPAND_FILL); + editor_box->add_child(shader_editor); + + FindReplaceBar *bar = memnew(FindReplaceBar); + main_container->add_child(bar); + bar->hide(); + shader_editor->set_find_replace_bar(bar); + + warnings_panel = memnew(RichTextLabel); + warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); + warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); + warnings_panel->set_meta_underline(true); + warnings_panel->set_selection_enabled(true); + warnings_panel->set_focus_mode(FOCUS_CLICK); + warnings_panel->hide(); + warnings_panel->connect("meta_clicked", callable_mp(this, &TextShaderEditor::_warning_clicked)); + editor_box->add_child(warnings_panel); + shader_editor->set_warnings_panel(warnings_panel); + + goto_line_dialog = memnew(GotoLineDialog); + add_child(goto_line_dialog); + + disk_changed = memnew(ConfirmationDialog); + + VBoxContainer *vbc = memnew(VBoxContainer); + disk_changed->add_child(vbc); + + Label *dl = memnew(Label); + dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?")); + vbc->add_child(dl); + + disk_changed->connect("confirmed", callable_mp(this, &TextShaderEditor::_reload)); + disk_changed->set_ok_button_text(TTR("Reload")); + + disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->connect("custom_action", callable_mp(this, &TextShaderEditor::save_external_data)); + + add_child(disk_changed); + + _editor_settings_changed(); +} diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h new file mode 100644 index 0000000000..abeaff1fff --- /dev/null +++ b/editor/plugins/text_shader_editor.h @@ -0,0 +1,199 @@ +/*************************************************************************/ +/* text_shader_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEXT_SHADER_EDITOR_H +#define TEXT_SHADER_EDITOR_H + +#include "editor/code_editor.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/rich_text_label.h" +#include "servers/rendering/shader_warnings.h" + +class GDShaderSyntaxHighlighter : public CodeHighlighter { + GDCLASS(GDShaderSyntaxHighlighter, CodeHighlighter) + +private: + Vector<Point2i> disabled_branch_regions; + Color disabled_branch_color; + +public: + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; + + void add_disabled_branch_region(const Point2i &p_region); + void clear_disabled_branch_regions(); + void set_disabled_branch_color(const Color &p_color); +}; + +class ShaderTextEditor : public CodeTextEditor { + GDCLASS(ShaderTextEditor, CodeTextEditor); + + Color marked_line_color = Color(1, 1, 1); + + struct WarningsComparator { + _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); } + }; + + Ref<GDShaderSyntaxHighlighter> syntax_highlighter; + RichTextLabel *warnings_panel = nullptr; + Ref<Shader> shader; + Ref<ShaderInclude> shader_inc; + List<ShaderWarning> warnings; + Error last_compile_result = Error::OK; + + void _check_shader_mode(); + void _update_warning_panel(); + + bool block_shader_changed = false; + void _shader_changed(); + + uint32_t dependencies_version = 0; // Incremented if deps changed + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _load_theme_settings() override; + + virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override; + +public: + void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; } + uint32_t get_dependencies_version() const { return dependencies_version; } + + virtual void _validate_script() override; + + void reload_text(); + void set_warnings_panel(RichTextLabel *p_warnings_panel); + + Ref<Shader> get_edited_shader() const; + Ref<ShaderInclude> get_edited_shader_include() const; + + void set_edited_shader(const Ref<Shader> &p_shader); + void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code); + void set_edited_code(const String &p_code); + + ShaderTextEditor(); +}; + +class TextShaderEditor : public MarginContainer { + GDCLASS(TextShaderEditor, MarginContainer); + + enum { + EDIT_UNDO, + EDIT_REDO, + EDIT_CUT, + EDIT_COPY, + EDIT_PASTE, + EDIT_SELECT_ALL, + EDIT_MOVE_LINE_UP, + EDIT_MOVE_LINE_DOWN, + EDIT_INDENT, + EDIT_UNINDENT, + EDIT_DELETE_LINE, + EDIT_DUPLICATE_SELECTION, + EDIT_TOGGLE_COMMENT, + EDIT_COMPLETE, + SEARCH_FIND, + SEARCH_FIND_NEXT, + SEARCH_FIND_PREV, + SEARCH_REPLACE, + SEARCH_GOTO_LINE, + BOOKMARK_TOGGLE, + BOOKMARK_GOTO_NEXT, + BOOKMARK_GOTO_PREV, + BOOKMARK_REMOVE_ALL, + HELP_DOCS, + }; + + MenuButton *edit_menu = nullptr; + MenuButton *search_menu = nullptr; + PopupMenu *bookmarks_menu = nullptr; + MenuButton *help_menu = nullptr; + PopupMenu *context_menu = nullptr; + RichTextLabel *warnings_panel = nullptr; + uint64_t idle = 0; + + GotoLineDialog *goto_line_dialog = nullptr; + ConfirmationDialog *erase_tab_confirm = nullptr; + ConfirmationDialog *disk_changed = nullptr; + + ShaderTextEditor *shader_editor = nullptr; + bool compilation_success = true; + + void _menu_option(int p_option); + mutable Ref<Shader> shader; + mutable Ref<ShaderInclude> shader_inc; + + void _editor_settings_changed(); + void _project_settings_changed(); + + void _check_for_external_edit(); + void _reload_shader_from_disk(); + void _reload_shader_include_from_disk(); + void _reload(); + void _show_warnings_panel(bool p_show); + void _warning_clicked(Variant p_line); + void _update_warnings(bool p_validate); + + void _script_validated(bool p_valid) { + compilation_success = p_valid; + emit_signal(SNAME("validation_changed")); + } + + uint32_t dependencies_version = 0xFFFFFFFF; + +protected: + void _notification(int p_what); + static void _bind_methods(); + void _make_context_menu(bool p_selection, Vector2 p_position); + void _text_edit_gui_input(const Ref<InputEvent> &p_ev); + + void _update_bookmark_list(); + void _bookmark_item_pressed(int p_idx); + +public: + bool was_compilation_successful() const { return compilation_success; } + void apply_shaders(); + void ensure_select_current(); + void edit(const Ref<Shader> &p_shader); + void edit(const Ref<ShaderInclude> &p_shader_inc); + void goto_line_selection(int p_line, int p_begin, int p_end); + void save_external_data(const String &p_str = ""); + void validate_script(); + bool is_unsaved() const; + + virtual Size2 get_minimum_size() const override { return Size2(0, 200); } + + TextShaderEditor(); +}; + +#endif // TEXT_SHADER_EDITOR_H diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 4d54001b94..d0f8e1f32d 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1977,6 +1977,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } } drag_last_pos = mm->get_position(); + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { int terrain_set = Dictionary(drag_painted_value)["terrain_set"]; int terrain = Dictionary(drag_painted_value)["terrain"]; @@ -2026,14 +2027,15 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } } drag_last_pos = mm->get_position(); + accept_event(); } } Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { - if (picker_button->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && picker_button->is_pressed()) { Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { @@ -2060,6 +2062,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t terrain_set_property_editor->update_property(); _update_terrain_selector(); picker_button->set_pressed(false); + accept_event(); } } else { Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); @@ -2071,6 +2074,10 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t int terrain_set = int(dummy_object->get("terrain_set")); int terrain = int(dummy_object->get("terrain")); if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { + // Paint terrain sets. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain_set = -1; + } if (mb->is_ctrl_pressed()) { // Paint terrain set with rect. drag_type = DRAG_TYPE_PAINT_TERRAIN_SET_RECT; @@ -2106,9 +2113,14 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } drag_last_pos = mb->get_position(); } + accept_event(); } else if (tile_data->get_terrain_set() == terrain_set) { + // Paint terrain bits. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain = -1; + } if (mb->is_ctrl_pressed()) { - // Paint terrain set with rect. + // Paint terrain bits with rect. drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS_RECT; drag_modified.clear(); Dictionary painted_dict; @@ -2163,6 +2175,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } drag_last_pos = mb->get_position(); } + accept_event(); } } } else { @@ -2190,18 +2203,21 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t for (const TileMapCell &E : edited) { Vector2i coords = E.get_atlas_coords(); TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_set()); undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.alternative_tile), drag_painted_value); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain()); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_data->is_valid_terrain_peering_bit(bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_peering_bit(bit)); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_set()); + if (tile_data->get_terrain_set() >= 0) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain()); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_data->is_valid_terrain_peering_bit(bit)) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_peering_bit(bit)); + } } } } undo_redo->commit_action(true); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Terrain Set")); for (KeyValue<TileMapCell, Variant> &E : drag_modified) { @@ -2209,17 +2225,20 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t Vector2i coords = E.key.get_atlas_coords(); undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); - Array array = dict["terrain_peering_bits"]; - for (int i = 0; i < array.size(); i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_terrain_peering_bit(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + if (int(dict["terrain_set"]) >= 0) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); + Array array = dict["terrain_peering_bits"]; + for (int i = 0; i < array.size(); i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(dict["terrain_set"], bit)) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + } } } } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2243,6 +2262,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2312,6 +2332,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } undo_redo->commit_action(true); drag_type = DRAG_TYPE_NONE; + accept_event(); } } } @@ -2348,6 +2369,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } drag_last_pos = mm->get_position(); + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2400,14 +2422,15 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } } drag_last_pos = mm->get_position(); + accept_event(); } } Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { - if (picker_button->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && picker_button->is_pressed()) { Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); Vector2i coords = Vector2i(tile.x, tile.y); int alternative_tile = tile.z; @@ -2437,6 +2460,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi terrain_set_property_editor->update_property(); _update_terrain_selector(); picker_button->set_pressed(false); + accept_event(); } } else { int terrain_set = int(dummy_object->get("terrain_set")); @@ -2446,75 +2470,87 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi Vector2i coords = Vector2i(tile.x, tile.y); int alternative_tile = tile.z; - TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); - if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { - drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; - drag_modified.clear(); - drag_painted_value = int(dummy_object->get("terrain_set")); - if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileMapCell cell; - cell.source_id = 0; - cell.set_atlas_coords(coords); - cell.alternative_tile = alternative_tile; - Dictionary dict; - dict["terrain_set"] = tile_data->get_terrain_set(); - Array array; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); + if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { + // Paint terrain sets. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain_set = -1; } - dict["terrain_peering_bits"] = array; - drag_modified[cell] = dict; - tile_data->set_terrain_set(drag_painted_value); - } - drag_last_pos = mb->get_position(); - } else if (tile_data->get_terrain_set() == terrain_set) { - // Paint terrain bits. - drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; - drag_modified.clear(); - Dictionary painted_dict; - painted_dict["terrain_set"] = terrain_set; - painted_dict["terrain"] = terrain; - drag_painted_value = painted_dict; - - if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileMapCell cell; - cell.source_id = 0; - cell.set_atlas_coords(coords); - cell.alternative_tile = alternative_tile; - - // Save the old terrain_set and terrains bits. - Dictionary dict; - dict["terrain_set"] = tile_data->get_terrain_set(); - dict["terrain"] = tile_data->get_terrain(); - Array array; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); + drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; + drag_modified.clear(); + drag_painted_value = int(dummy_object->get("terrain_set")); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + tile_data->set_terrain_set(drag_painted_value); } - dict["terrain_peering_bits"] = array; - drag_modified[cell] = dict; + drag_last_pos = mb->get_position(); + accept_event(); + } else if (tile_data->get_terrain_set() == terrain_set) { + // Paint terrain bits. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain = -1; + } + // Paint terrain bits. + drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; + drag_modified.clear(); + Dictionary painted_dict; + painted_dict["terrain_set"] = terrain_set; + painted_dict["terrain"] = terrain; + drag_painted_value = painted_dict; - // Set the terrain bit. - Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; - Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); - if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { - tile_data->set_terrain(terrain); - } - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { - polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); - if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { - tile_data->set_terrain_peering_bit(bit, terrain); + // Save the old terrain_set and terrains bits. + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + + // Set the terrain bit. + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_terrain(terrain); + } + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_terrain_peering_bit(bit, terrain); + } } } } + drag_last_pos = mb->get_position(); + accept_event(); } - drag_last_pos = mb->get_position(); } } } else { @@ -2523,16 +2559,19 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi for (KeyValue<TileMapCell, Variant> &E : drag_modified) { Dictionary dict = E.value; Vector2i coords = E.key.get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); - Array array = dict["terrain_peering_bits"]; - for (int i = 0; i < array.size(); i++) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); + if (int(dict["terrain_set"]) >= 0) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); + Array array = dict["terrain_peering_bits"]; + for (int i = 0; i < array.size(); i++) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + } } } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2556,6 +2595,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } } } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 395c9b0248..acfa8b3d00 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -2428,20 +2428,16 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_line(Vector2i return HashMap<Vector2i, TileMapCell>(); } - if (selected_type == SELECTED_TYPE_CONNECT) { - return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, true); - } else if (selected_type == SELECTED_TYPE_PATH) { - return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, false); - } else { // SELECTED_TYPE_PATTERN - TileSet::TerrainsPattern terrains_pattern; - if (p_erase) { - terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); - } else { - terrains_pattern = selected_terrains_pattern; + if (p_erase) { + return _draw_terrain_pattern(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set)); + } else { + if (selected_type == SELECTED_TYPE_CONNECT) { + return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, true); + } else if (selected_type == SELECTED_TYPE_PATH) { + return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, false); + } else { // SELECTED_TYPE_PATTERN + return _draw_terrain_pattern(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrains_pattern); } - - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell); - return _draw_terrain_pattern(line, selected_terrain_set, terrains_pattern); } } @@ -2468,16 +2464,14 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_rect(Vector2i } } - if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) { - return _draw_terrain_path_or_connect(to_draw, selected_terrain_set, selected_terrain, true); - } else { // SELECTED_TYPE_PATTERN - TileSet::TerrainsPattern terrains_pattern; - if (p_erase) { - terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); - } else { - terrains_pattern = selected_terrains_pattern; + if (p_erase) { + return _draw_terrain_pattern(to_draw, selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set)); + } else { + if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) { + return _draw_terrain_path_or_connect(to_draw, selected_terrain_set, selected_terrain, true); + } else { // SELECTED_TYPE_PATTERN + return _draw_terrain_pattern(to_draw, selected_terrain_set, selected_terrains_pattern); } - return _draw_terrain_pattern(to_draw, selected_terrain_set, terrains_pattern); } } @@ -2609,16 +2603,14 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_bucket_fill(Ve cells_to_draw_as_vector.append(cell); } - if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) { - return _draw_terrain_path_or_connect(cells_to_draw_as_vector, selected_terrain_set, selected_terrain, true); - } else { // SELECTED_TYPE_PATTERN - TileSet::TerrainsPattern terrains_pattern; - if (p_erase) { - terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); - } else { - terrains_pattern = selected_terrains_pattern; + if (p_erase) { + return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set)); + } else { + if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) { + return _draw_terrain_path_or_connect(cells_to_draw_as_vector, selected_terrain_set, selected_terrain, true); + } else { // SELECTED_TYPE_PATTERN + return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, selected_terrains_pattern); } - return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, terrains_pattern); } } diff --git a/editor/translations/extract.py b/editor/translations/extract.py index 07026baee2..cecdb3939d 100755 --- a/editor/translations/extract.py +++ b/editor/translations/extract.py @@ -8,6 +8,7 @@ import re import shutil import subprocess import sys +from typing import Dict, Tuple class Message: @@ -42,7 +43,7 @@ class Message: return "\n".join(lines) -messages_map = {} # (id, context) -> Message. +messages_map: Dict[Tuple[str, str], Message] = {} # (id, context) -> Message. line_nb = False @@ -51,11 +52,11 @@ for arg in sys.argv[1:]: print("Enabling line numbers in the context locations.") line_nb = True else: - os.sys.exit("Non supported argument '" + arg + "'. Aborting.") + sys.exit("Non supported argument '" + arg + "'. Aborting.") if not os.path.exists("editor"): - os.sys.exit("ERROR: This script should be started from the root of the git repo.") + sys.exit("ERROR: This script should be started from the root of the git repo.") matches = [] diff --git a/gles3_builders.py b/gles3_builders.py index eafe503dd5..84f11532e0 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -3,6 +3,10 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ +import os.path + +from typing import Optional + from platform_methods import subprocess_main @@ -30,7 +34,7 @@ class GLES3HeaderStruct: self.specialization_values = [] -def include_file_in_gles3_header(filename, header_data, depth): +def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct, depth: int): fs = open(filename, "r") line = fs.readline() @@ -91,8 +95,6 @@ def include_file_in_gles3_header(filename, header_data, depth): while line.find("#include ") != -1: includeline = line.replace("#include ", "").strip()[1:-1] - import os.path - included_file = os.path.relpath(os.path.dirname(filename) + "/" + includeline) if not included_file in header_data.vertex_included_files and header_data.reading == "vertex": header_data.vertex_included_files += [included_file] @@ -182,7 +184,7 @@ def include_file_in_gles3_header(filename, header_data, depth): return header_data -def build_gles3_header(filename, include, class_suffix, header_data=None): +def build_gles3_header(filename: str, include: str, class_suffix: str, header_data: Optional[GLES3HeaderStruct] = None): header_data = header_data or GLES3HeaderStruct() include_file_in_gles3_header(filename, header_data, 0) diff --git a/glsl_builders.py b/glsl_builders.py index 8cb5807f21..888f541cf4 100644 --- a/glsl_builders.py +++ b/glsl_builders.py @@ -4,13 +4,15 @@ All such functions are invoked in a subprocess on Windows to prevent build flaki """ import os.path +from typing import Optional, Iterable + from platform_methods import subprocess_main -def generate_inline_code(input_lines, insert_newline=True): +def generate_inline_code(input_lines: Iterable[str], insert_newline: bool = True): """Take header data and generate inline code - :param: list input_lines: values for shared inline code + :param: input_lines: values for shared inline code :return: str - generated inline value """ output = [] @@ -40,7 +42,7 @@ class RDHeaderStruct: self.compute_offset = 0 -def include_file_in_rd_header(filename, header_data, depth): +def include_file_in_rd_header(filename: str, header_data: RDHeaderStruct, depth: int) -> RDHeaderStruct: fs = open(filename, "r") line = fs.readline() @@ -112,7 +114,7 @@ def include_file_in_rd_header(filename, header_data, depth): return header_data -def build_rd_header(filename, header_data=None): +def build_rd_header(filename: str, header_data: Optional[RDHeaderStruct] = None) -> None: header_data = header_data or RDHeaderStruct() include_file_in_rd_header(filename, header_data, 0) @@ -171,7 +173,7 @@ class RAWHeaderStruct: self.code = "" -def include_file_in_raw_header(filename, header_data, depth): +def include_file_in_raw_header(filename: str, header_data: RAWHeaderStruct, depth: int) -> None: fs = open(filename, "r") line = fs.readline() @@ -191,7 +193,7 @@ def include_file_in_raw_header(filename, header_data, depth): fs.close() -def build_raw_header(filename, header_data=None): +def build_raw_header(filename: str, header_data: Optional[RAWHeaderStruct] = None): header_data = header_data or RAWHeaderStruct() include_file_in_raw_header(filename, header_data, 0) diff --git a/methods.py b/methods.py index 09012b8763..7feffb2848 100644 --- a/methods.py +++ b/methods.py @@ -1,6 +1,6 @@ import os -import re import sys +import re import glob import subprocess from collections import OrderedDict @@ -663,7 +663,6 @@ def detect_visual_c_compiler_version(tools_env): if vc_x86_amd64_compiler_detection_index > -1 and ( vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_amd64_compiler_detection_index ): - vc_chosen_compiler_index = vc_x86_amd64_compiler_detection_index vc_chosen_compiler_str = "x86_amd64" return vc_chosen_compiler_str diff --git a/misc/scripts/mypy.ini b/misc/scripts/mypy.ini new file mode 100644 index 0000000000..c1ea695ca5 --- /dev/null +++ b/misc/scripts/mypy.ini @@ -0,0 +1,11 @@ +[mypy] +ignore_missing_imports = true +disallow_any_generics = True +pretty = True +show_column_numbers = True +warn_redundant_casts = True +warn_return_any = True +warn_unreachable = True + +namespace_packages = True +explicit_package_bases = True diff --git a/misc/scripts/mypy_check.sh b/misc/scripts/mypy_check.sh new file mode 100755 index 0000000000..2a06486d67 --- /dev/null +++ b/misc/scripts/mypy_check.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -uo pipefail + +echo -e "Python: mypy static analysis..." +mypy --config-file=./misc/scripts/mypy.ini . diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index b4da94e448..f1c841e9dc 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2937,6 +2937,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre push_error(R"(Expected expression as the function argument.)"); } else { call->arguments.push_back(argument); + + if (argument->type == Node::IDENTIFIER && current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) { + completion_context.type = COMPLETION_IDENTIFIER; + } } ct = COMPLETION_CALL_ARGUMENTS; } while (match(GDScriptTokenizer::Token::COMMA)); diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index ab52761e17..f79731dd22 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -261,7 +261,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li #define ADD_OPTION_ENUM(PATH, ENUM_HINT, VALUE) \ r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE)); - ADD_OPTION_ENUM("blender/nodes/visible", "Visible Only,Renderable,All", BLEND_VISIBLE_ALL); + ADD_OPTION_ENUM("blender/nodes/visible", "All,Visible Only,Renderable", BLEND_VISIBLE_ALL); ADD_OPTION_BOOL("blender/nodes/punctual_lights", true); ADD_OPTION_BOOL("blender/nodes/cameras", true); ADD_OPTION_BOOL("blender/nodes/custom_properties", true); diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index dd1c1b9889..a1485ff82e 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -45,9 +45,9 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { public: enum { + BLEND_VISIBLE_ALL, BLEND_VISIBLE_VISIBLE_ONLY, - BLEND_VISIBLE_RENDERABLE, - BLEND_VISIBLE_ALL + BLEND_VISIBLE_RENDERABLE }; enum { BLEND_BONE_INFLUENCES_NONE, diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index d78a9c7db8..d28c3a0c3a 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -5,6 +5,7 @@ import os.path import shlex import subprocess from dataclasses import dataclass +from typing import Optional, List def find_dotnet_cli(): @@ -150,10 +151,7 @@ def find_any_msbuild_tool(mono_prefix): return None -def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): - if msbuild_args is None: - msbuild_args = [] - +def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: Optional[List[str]] = None): using_msbuild_mono = False # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild @@ -169,7 +167,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): args += [sln] - if len(msbuild_args) > 0: + if msbuild_args: args += msbuild_args print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index fbd59d649f..9c3bc51c44 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -4,6 +4,21 @@ using System.Runtime.InteropServices; namespace Godot { /// <summary> + /// Specifies which order Euler angle rotations should be in. + /// When composing, the order is the same as the letters. When decomposing, + /// the order is reversed (ex: YXZ decomposes Z first, then X, and Y last). + /// </summary> + public enum EulerOrder + { + XYZ, + XZY, + YXZ, + YZX, + ZXY, + ZYX + }; + + /// <summary> /// 3×3 matrix used for 3D rotation and scale. /// Almost always used as an orthogonal basis for a Transform. /// @@ -244,50 +259,258 @@ namespace Godot } /// <summary> - /// Returns the basis's rotation in the form of Euler angles - /// (in the YXZ convention: when *decomposing*, first Z, then X, and Y last). - /// The returned vector contains the rotation angles in - /// the format (X angle, Y angle, Z angle). + /// Returns the basis's rotation in the form of Euler angles. + /// The Euler order depends on the [param order] parameter, + /// by default it uses the YXZ convention: when decomposing, + /// first Z, then X, and Y last. The returned vector contains + /// the rotation angles in the format (X angle, Y angle, Z angle). /// /// Consider using the <see cref="GetRotationQuaternion"/> method instead, which /// returns a <see cref="Quaternion"/> quaternion instead of Euler angles. /// </summary> + /// <param name="order">The Euler order to use. By default, use YXZ order (most common).</param> /// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns> - public Vector3 GetEuler() + public Vector3 GetEuler(EulerOrder order = EulerOrder.YXZ) { - Basis m = Orthonormalized(); - - Vector3 euler; - euler.z = 0.0f; - - real_t mzy = m.Row1[2]; - - if (mzy < 1.0f) + switch (order) { - if (mzy > -1.0f) + case EulerOrder.XYZ: { - euler.x = Mathf.Asin(-mzy); - euler.y = Mathf.Atan2(m.Row0[2], m.Row2[2]); - euler.z = Mathf.Atan2(m.Row1[0], m.Row1[1]); + // Euler angles in XYZ convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz -cy*sz sy + // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx + // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy + Vector3 euler; + real_t sy = Row0[2]; + if (sy < (1.0f - Mathf.Epsilon)) + { + if (sy > -(1.0f - Mathf.Epsilon)) + { + // is this a pure Y rotation? + if (Row1[0] == 0 && Row0[1] == 0 && Row1[2] == 0 && Row2[1] == 0 && Row1[1] == 1) + { + // return the simplest form (human friendlier in editor and scripts) + euler.x = 0; + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = 0; + } + else + { + euler.x = Mathf.Atan2(-Row1[2], Row2[2]); + euler.y = Mathf.Asin(sy); + euler.z = Mathf.Atan2(-Row0[1], Row0[0]); + } + } + else + { + euler.x = Mathf.Atan2(Row2[1], Row1[1]); + euler.y = -Mathf.Tau / 4.0f; + euler.z = 0.0f; + } + } + else + { + euler.x = Mathf.Atan2(Row2[1], Row1[1]); + euler.y = Mathf.Tau / 4.0f; + euler.z = 0.0f; + } + return euler; } - else + case EulerOrder.XZY: { - euler.x = Mathf.Pi * 0.5f; - euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]); + // Euler angles in XZY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy -sz cz*sy + // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx + // cy*sx*sz cz*sx cx*cy+sx*sz*sy + Vector3 euler; + real_t sz = Row0[1]; + if (sz < (1.0f - Mathf.Epsilon)) + { + if (sz > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Atan2(Row2[1], Row1[1]); + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = Mathf.Asin(-sz); + } + else + { + // It's -1 + euler.x = -Mathf.Atan2(Row1[2], Row2[2]); + euler.y = 0.0f; + euler.z = Mathf.Tau / 4.0f; + } + } + else + { + // It's 1 + euler.x = -Mathf.Atan2(Row1[2], Row2[2]); + euler.y = 0.0f; + euler.z = -Mathf.Tau / 4.0f; + } + return euler; } - } - else - { - euler.x = -Mathf.Pi * 0.5f; - euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]); - } + case EulerOrder.YXZ: + { + // Euler angles in YXZ convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy + // cx*sz cx*cz -sx + // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx + Vector3 euler; + real_t m12 = Row1[2]; + if (m12 < (1 - Mathf.Epsilon)) + { + if (m12 > -(1 - Mathf.Epsilon)) + { + // is this a pure X rotation? + if (Row1[0] == 0 && Row0[1] == 0 && Row0[2] == 0 && Row2[0] == 0 && Row0[0] == 1) + { + // return the simplest form (human friendlier in editor and scripts) + euler.x = Mathf.Atan2(-m12, Row1[1]); + euler.y = 0; + euler.z = 0; + } + else + { + euler.x = Mathf.Asin(-m12); + euler.y = Mathf.Atan2(Row0[2], Row2[2]); + euler.z = Mathf.Atan2(Row1[0], Row1[1]); + } + } + else + { // m12 == -1 + euler.x = Mathf.Tau / 4.0f; + euler.y = Mathf.Atan2(Row0[1], Row0[0]); + euler.z = 0; + } + } + else + { // m12 == 1 + euler.x = -Mathf.Tau / 4.0f; + euler.y = -Mathf.Atan2(Row0[1], Row0[0]); + euler.z = 0; + } - return euler; + return euler; + } + case EulerOrder.YZX: + { + // Euler angles in YZX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx + // sz cz*cx -cz*sx + // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx + Vector3 euler; + real_t sz = Row1[0]; + if (sz < (1.0f - Mathf.Epsilon)) + { + if (sz > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Atan2(-Row1[2], Row1[1]); + euler.y = Mathf.Atan2(-Row2[0], Row0[0]); + euler.z = Mathf.Asin(sz); + } + else + { + // It's -1 + euler.x = Mathf.Atan2(Row2[1], Row2[2]); + euler.y = 0.0f; + euler.z = -Mathf.Tau / 4.0f; + } + } + else + { + // It's 1 + euler.x = Mathf.Atan2(Row2[1], Row2[2]); + euler.y = 0.0f; + euler.z = Mathf.Tau / 4.0f; + } + return euler; + } + case EulerOrder.ZXY: + { + // Euler angles in ZXY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx + // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx + // -cx*sy sx cx*cy + Vector3 euler; + real_t sx = Row2[1]; + if (sx < (1.0f - Mathf.Epsilon)) + { + if (sx > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Asin(sx); + euler.y = Mathf.Atan2(-Row2[0], Row2[2]); + euler.z = Mathf.Atan2(-Row0[1], Row1[1]); + } + else + { + // It's -1 + euler.x = -Mathf.Tau / 4.0f; + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = 0; + } + } + else + { + // It's 1 + euler.x = Mathf.Tau / 4.0f; + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = 0; + } + return euler; + } + case EulerOrder.ZYX: + { + // Euler angles in ZYX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy + // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx + // -sy cy*sx cy*cx + Vector3 euler; + real_t sy = Row2[0]; + if (sy < (1.0f - Mathf.Epsilon)) + { + if (sy > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Atan2(Row2[1], Row2[2]); + euler.y = Mathf.Asin(-sy); + euler.z = Mathf.Atan2(Row1[0], Row0[0]); + } + else + { + // It's -1 + euler.x = 0; + euler.y = Mathf.Tau / 4.0f; + euler.z = -Mathf.Atan2(Row0[1], Row1[1]); + } + } + else + { + // It's 1 + euler.x = 0; + euler.y = -Mathf.Tau / 4.0f; + euler.z = -Mathf.Atan2(Row0[1], Row1[1]); + } + return euler; + } + default: + throw new ArgumentOutOfRangeException(nameof(order)); + } } /// <summary> /// Returns the basis's rotation in the form of a quaternion. - /// See <see cref="GetEuler()"/> if you need Euler angles, but keep in + /// See <see cref="GetEuler"/> if you need Euler angles, but keep in /// mind that quaternions should generally be preferred to Euler angles. /// </summary> /// <returns>A <see cref="Quaternion"/> representing the basis's rotation.</returns> @@ -712,35 +935,6 @@ namespace Godot } /// <summary> - /// Constructs a pure rotation basis matrix from the given Euler angles - /// (in the YXZ convention: when *composing*, first Y, then X, and Z last), - /// given in the vector format as (X angle, Y angle, Z angle). - /// - /// Consider using the <see cref="Basis(Quaternion)"/> constructor instead, which - /// uses a <see cref="Quaternion"/> quaternion instead of Euler angles. - /// </summary> - /// <param name="eulerYXZ">The Euler angles to create the basis from.</param> - public Basis(Vector3 eulerYXZ) - { - real_t c; - real_t s; - - c = Mathf.Cos(eulerYXZ.x); - s = Mathf.Sin(eulerYXZ.x); - var xmat = new Basis(1, 0, 0, 0, c, -s, 0, s, c); - - c = Mathf.Cos(eulerYXZ.y); - s = Mathf.Sin(eulerYXZ.y); - var ymat = new Basis(c, 0, s, 0, 1, 0, -s, 0, c); - - c = Mathf.Cos(eulerYXZ.z); - s = Mathf.Sin(eulerYXZ.z); - var zmat = new Basis(c, -s, 0, s, c, 0, 0, 0, 1); - - this = ymat * xmat * zmat; - } - - /// <summary> /// Constructs a pure rotation basis matrix, rotated around the given <paramref name="axis"/> /// by <paramref name="angle"/> (in radians). The axis must be a normalized vector. /// </summary> @@ -800,6 +994,46 @@ namespace Godot } /// <summary> + /// Constructs a Basis matrix from Euler angles in the specified rotation order. By default, use YXZ order (most common). + /// </summary> + /// <param name="euler">The Euler angles to use.</param> + /// <param name="order">The order to compose the Euler angles.</param> + public static Basis FromEuler(Vector3 euler, EulerOrder order = EulerOrder.YXZ) + { + real_t c, s; + + c = Mathf.Cos(euler.x); + s = Mathf.Sin(euler.x); + Basis xmat = new Basis(new Vector3(1, 0, 0), new Vector3(0, c, s), new Vector3(0, -s, c)); + + c = Mathf.Cos(euler.y); + s = Mathf.Sin(euler.y); + Basis ymat = new Basis(new Vector3(c, 0, -s), new Vector3(0, 1, 0), new Vector3(s, 0, c)); + + c = Mathf.Cos(euler.z); + s = Mathf.Sin(euler.z); + Basis zmat = new Basis(new Vector3(c, s, 0), new Vector3(-s, c, 0), new Vector3(0, 0, 1)); + + switch (order) + { + case EulerOrder.XYZ: + return xmat * ymat * zmat; + case EulerOrder.XZY: + return xmat * zmat * ymat; + case EulerOrder.YXZ: + return ymat * xmat * zmat; + case EulerOrder.YZX: + return ymat * zmat * xmat; + case EulerOrder.ZXY: + return zmat * xmat * ymat; + case EulerOrder.ZYX: + return zmat * ymat * xmat; + default: + throw new ArgumentOutOfRangeException(nameof(order)); + } + } + + /// <summary> /// Constructs a pure scale basis matrix with no rotation or shearing. /// The scale values are set as the main diagonal of the matrix, /// and all of the other parts of the matrix are zero. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index 1b7f5158fd..bdedd2e87a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -72,7 +72,7 @@ namespace Godot /// <param name="delegate">Delegate method that will be called.</param> public Callable(Delegate @delegate) { - _target = null; + _target = @delegate?.Target as Object; _method = null; _delegate = @delegate; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 140fc167ba..76b186cd15 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -721,8 +721,9 @@ namespace Godot.NativeInterop if (p_managed_callable.Delegate != null) { var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate); + IntPtr objectPtr = p_managed_callable.Target != null ? Object.GetPtr(p_managed_callable.Target) : IntPtr.Zero; NativeFuncs.godotsharp_callable_new_with_delegate( - GCHandle.ToIntPtr(gcHandle), out godot_callable callable); + GCHandle.ToIntPtr(gcHandle), objectPtr, out godot_callable callable); return callable; } else diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index bd00611383..20ede9f0dd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -141,7 +141,7 @@ namespace Godot.NativeInterop public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest, in godot_string p_element); - public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, + public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_object, out godot_callable r_callable); internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable, diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 276701cdaa..2717b945f6 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -447,9 +447,10 @@ void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String r_dest->append(*p_element); } -void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, Callable *r_callable) { +void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, const Object *p_object, Callable *r_callable) { // TODO: Use pooling for ManagedCallable instances. - CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle)); + ObjectID objid = p_object ? p_object->get_instance_id() : ObjectID(); + CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, objid)); memnew_placement(r_callable, Callable(managed_callable)); } diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index 9305dc645a..0c2c533090 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -79,7 +79,9 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const { } ObjectID ManagedCallable::get_object() const { - // TODO: If the delegate target extends Godot.Object, use that instead! + if (object_id != ObjectID()) { + return object_id; + } return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id(); } @@ -104,7 +106,7 @@ void ManagedCallable::release_delegate_handle() { // Why you do this clang-format... /* clang-format off */ -ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle) : delegate_handle(p_delegate_handle) { +ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) { #ifdef GD_MONO_HOT_RELOAD { MutexLock lock(instances_mutex); diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h index aa3344f4d5..26cd164fb6 100644 --- a/modules/mono/managed_callable.h +++ b/modules/mono/managed_callable.h @@ -40,6 +40,7 @@ class ManagedCallable : public CallableCustom { friend class CSharpLanguage; GCHandleIntPtr delegate_handle; + ObjectID object_id; #ifdef GD_MONO_HOT_RELOAD SelfList<ManagedCallable> self_instance = this; @@ -66,7 +67,7 @@ public: void release_delegate_handle(); - ManagedCallable(GCHandleIntPtr p_delegate_handle); + ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id); ~ManagedCallable(); }; diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 6e3dbfab47..53d8e82dfc 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -125,9 +125,12 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + const ObjectID oid = node->get_instance_id(); + MultiplayerSpawner *spawner = rep_state->get_spawner(oid); + ERR_FAIL_COND_V_MSG(spawner && spawner->get_multiplayer_authority() != sync->get_multiplayer_authority(), ERR_INVALID_PARAMETER, "The authority of the MultiplayerSynchronizer \"" + String(sync->get_path()) + "\" differs from the authority of its \"root_node\" spawner and will not sync. Change the \"root_node\" of the MultiplayerSynchronizer to be a child of the scene root instead."); + // Add to synchronizer list and setup visibility. rep_state->config_add_sync(node, sync); - const ObjectID oid = node->get_instance_id(); sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed).bind(oid)); if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { _update_sync_visibility(0, oid); diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 394c32f20d..83862e1e34 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -238,6 +238,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p to_visit.clear(); to_visit.push_back(0); least_cost_id = 0; + prev_least_cost_id = -1; reachable_end = nullptr; diff --git a/platform/android/detect.py b/platform/android/detect.py index f3e3f80dd5..e541aa0373 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -3,6 +3,11 @@ import sys import platform import subprocess +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment + def is_active(): return True @@ -17,8 +22,6 @@ def can_build(): def get_opts(): - from SCons.Variables import BoolVariable, EnumVariable - return [ ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_env_android_sdk_root()), ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"), @@ -74,7 +77,7 @@ def install_ndk_if_needed(env): env["ANDROID_NDK_ROOT"] = get_android_ndk_root(env) -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_32", "x86_64", "arm32", "arm64"] if env["arch"] not in supported_arches: diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 74561e9fc5..38e62134b5 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -2,6 +2,11 @@ import os import sys from methods import detect_darwin_sdk_path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment + def is_active(): return True @@ -42,7 +47,7 @@ def get_flags(): ] -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_64", "arm64"] if env["arch"] not in supported_arches: diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 92af7e2d75..dfde0d249c 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -4,6 +4,11 @@ import sys from methods import get_compiler_version, using_gcc from platform_methods import detect_arch +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment + def is_active(): return True @@ -55,7 +60,7 @@ def get_flags(): ] -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64"] if env["arch"] not in supported_arches: diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 66dea6cf1b..81fc941608 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -3142,6 +3142,11 @@ void DisplayServerX11::_window_changed(XEvent *event) { return; } + // Query display server about a possible new window state. + wd.fullscreen = _window_fullscreen_check(window_id); + wd.minimized = _window_minimize_check(window_id); + wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE"); + { //the position in xconfigure is not useful here, obtain it manually int x, y; diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 58d209cc7b..511286d52b 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -3,6 +3,11 @@ import sys from methods import detect_darwin_sdk_path from platform_methods import detect_arch +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment + def is_active(): return True @@ -72,7 +77,7 @@ def get_mvk_sdk_path(): return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/") -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_64", "arm64"] if env["arch"] not in supported_arches: diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index 2c5746cb06..64fe5bc4a2 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -3,6 +3,11 @@ import os import sys from platform_methods import detect_arch +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment + def is_active(): return True @@ -39,7 +44,7 @@ def get_flags(): ] -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_32", "x86_64", "arm32"] if env["arch"] not in supported_arches: @@ -83,7 +88,7 @@ def configure(env): env.AppendUnique(CCFLAGS=["/utf-8"]) # ANGLE - angle_root = os.getenv("ANGLE_SRC_PATH") + angle_root = os.environ["ANGLE_SRC_PATH"] env.Prepend(CPPPATH=[angle_root + "/include"]) jobs = str(env.GetOption("num_jobs")) angle_build_cmd = ( @@ -94,7 +99,7 @@ def configure(env): + " /p:Configuration=Release /p:Platform=" ) - if os.path.isfile(str(os.getenv("ANGLE_SRC_PATH")) + "/winrt/10/src/angle.sln"): + if os.path.isfile(f"{angle_root}/winrt/10/src/angle.sln"): env["build_angle"] = True ## Architecture diff --git a/platform/web/detect.py b/platform/web/detect.py index 77921847a8..08c1ff7b4a 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -11,6 +11,10 @@ from emscripten_helpers import ( ) from methods import get_compiler_version from SCons.Util import WhereIs +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment def is_active(): @@ -60,7 +64,7 @@ def get_flags(): ] -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["wasm32"] if env["arch"] not in supported_arches: diff --git a/platform/windows/detect.py b/platform/windows/detect.py index b184da49e4..a5d8d0344b 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -4,6 +4,11 @@ import subprocess import sys from platform_methods import detect_arch +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SCons import Environment + # To match other platforms STACK_SIZE = 8388608 @@ -588,7 +593,7 @@ def configure_mingw(env): env.Append(BUILDERS={"RES": env.Builder(action=build_res_file, suffix=".o", src_suffix=".rc")}) -def configure(env): +def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_32", "x86_64", "arm32", "arm64"] if env["arch"] not in supported_arches: diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index ae83e2136f..65f3767449 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -5275,6 +5275,7 @@ void TileData::set_terrain_set(int p_terrain_set) { } if (tile_set) { ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count()); + terrain = -1; for (int i = 0; i < 16; i++) { terrain_peering_bits[i] = -1; } diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index cde241f231..1e70f6880c 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -230,6 +230,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { float sh[9 * 4]; }; + // When changing any of these enums, remember to change the corresponding enums in the shader files as well. enum { INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4, INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5, diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index 1b31d2749d..a53872fc88 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -371,7 +371,7 @@ protected: /* Geometry instance */ - // check which ones of these apply, probably all except GI and SDFGI + // When changing any of these enums, remember to change the corresponding enums in the shader files as well. enum { INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4, INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5, diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl index e8e2dce990..0d36b98645 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl @@ -62,13 +62,14 @@ layout(set = 0, binding = 3) uniform sampler decal_sampler; layout(set = 0, binding = 4) uniform sampler light_projector_sampler; -#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 5) -#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6) -#define INSTANCE_FLAGS_USE_SDFGI (1 << 7) -#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8) -#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9) -#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10) -#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 11) +#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 4) +#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 5) +#define INSTANCE_FLAGS_USE_SDFGI (1 << 6) +#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 7) +#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 8) +#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 9) +#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 10) +#define INSTANCE_FLAGS_PARTICLES (1 << 11) #define INSTANCE_FLAGS_MULTIMESH (1 << 12) #define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13) #define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14) diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl index 5e4999fa0f..631ff0575b 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl @@ -55,13 +55,14 @@ layout(set = 0, binding = 2) uniform sampler shadow_sampler; layout(set = 0, binding = 3) uniform sampler decal_sampler; layout(set = 0, binding = 4) uniform sampler light_projector_sampler; -#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 5) -#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6) -#define INSTANCE_FLAGS_USE_SDFGI (1 << 7) -#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8) -#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9) -#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10) -#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 11) +#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 4) +#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 5) +#define INSTANCE_FLAGS_USE_SDFGI (1 << 6) +#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 7) +#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 8) +#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 9) +#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 10) +#define INSTANCE_FLAGS_PARTICLES (1 << 11) #define INSTANCE_FLAGS_MULTIMESH (1 << 12) #define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13) #define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14) diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h index 52118fa943..be271bad1f 100644 --- a/tests/core/math/test_vector3.h +++ b/tests/core/math/test_vector3.h @@ -84,16 +84,12 @@ TEST_CASE("[Vector3] Axis methods") { vector.min_axis_index() == Vector3::Axis::AXIS_X, "Vector3 min_axis_index should work as expected."); CHECK_MESSAGE( - vector.get_axis(vector.max_axis_index()) == (real_t)5.6, - "Vector3 get_axis should work as expected."); + vector[vector.max_axis_index()] == (real_t)5.6, + "Vector3 array operator should work as expected."); CHECK_MESSAGE( vector[vector.min_axis_index()] == (real_t)1.2, "Vector3 array operator should work as expected."); - vector.set_axis(Vector3::Axis::AXIS_Y, 4.7); - CHECK_MESSAGE( - vector.get_axis(Vector3::Axis::AXIS_Y) == (real_t)4.7, - "Vector3 set_axis should work as expected."); vector[Vector3::Axis::AXIS_Y] = 3.7; CHECK_MESSAGE( vector[Vector3::Axis::AXIS_Y] == (real_t)3.7, diff --git a/tests/core/math/test_vector3i.h b/tests/core/math/test_vector3i.h index 6c52781556..2050b222d0 100644 --- a/tests/core/math/test_vector3i.h +++ b/tests/core/math/test_vector3i.h @@ -53,16 +53,12 @@ TEST_CASE("[Vector3i] Axis methods") { vector.min_axis_index() == Vector3i::Axis::AXIS_X, "Vector3i min_axis_index should work as expected."); CHECK_MESSAGE( - vector.get_axis(vector.max_axis_index()) == 3, - "Vector3i get_axis should work as expected."); + vector[vector.max_axis_index()] == 3, + "Vector3i array operator should work as expected."); CHECK_MESSAGE( vector[vector.min_axis_index()] == 1, "Vector3i array operator should work as expected."); - vector.set_axis(Vector3i::Axis::AXIS_Y, 4); - CHECK_MESSAGE( - vector.get_axis(Vector3i::Axis::AXIS_Y) == 4, - "Vector3i set_axis should work as expected."); vector[Vector3i::Axis::AXIS_Y] = 5; CHECK_MESSAGE( vector[Vector3i::Axis::AXIS_Y] == 5, diff --git a/tests/core/math/test_vector4.h b/tests/core/math/test_vector4.h index 25ec8929b8..3f50f16635 100644 --- a/tests/core/math/test_vector4.h +++ b/tests/core/math/test_vector4.h @@ -55,16 +55,12 @@ TEST_CASE("[Vector4] Axis methods") { vector.min_axis_index() == Vector4::Axis::AXIS_W, "Vector4 min_axis_index should work as expected."); CHECK_MESSAGE( - vector.get_axis(vector.max_axis_index()) == (real_t)5.6, - "Vector4 get_axis should work as expected."); + vector[vector.max_axis_index()] == (real_t)5.6, + "Vector4 array operator should work as expected."); CHECK_MESSAGE( vector[vector.min_axis_index()] == (real_t)-0.9, "Vector4 array operator should work as expected."); - vector.set_axis(Vector4::Axis::AXIS_Y, 4.7); - CHECK_MESSAGE( - vector.get_axis(Vector4::Axis::AXIS_Y) == (real_t)4.7, - "Vector4 set_axis should work as expected."); vector[Vector4::Axis::AXIS_Y] = 3.7; CHECK_MESSAGE( vector[Vector4::Axis::AXIS_Y] == (real_t)3.7, diff --git a/tests/core/math/test_vector4i.h b/tests/core/math/test_vector4i.h index e106099914..309162c3f7 100644 --- a/tests/core/math/test_vector4i.h +++ b/tests/core/math/test_vector4i.h @@ -53,16 +53,12 @@ TEST_CASE("[Vector4i] Axis methods") { vector.min_axis_index() == Vector4i::Axis::AXIS_X, "Vector4i min_axis_index should work as expected."); CHECK_MESSAGE( - vector.get_axis(vector.max_axis_index()) == 4, - "Vector4i get_axis should work as expected."); + vector[vector.max_axis_index()] == 4, + "Vector4i array operator should work as expected."); CHECK_MESSAGE( vector[vector.min_axis_index()] == 1, "Vector4i array operator should work as expected."); - vector.set_axis(Vector4i::Axis::AXIS_Y, 5); - CHECK_MESSAGE( - vector.get_axis(Vector4i::Axis::AXIS_Y) == 5, - "Vector4i set_axis should work as expected."); vector[Vector4i::Axis::AXIS_Y] = 5; CHECK_MESSAGE( vector[Vector4i::Axis::AXIS_Y] == 5, diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index d97da05c04..969f5fc096 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -740,6 +740,14 @@ TEST_CASE("[String] sprintf") { REQUIRE(error == false); CHECK(output == String("fish 99.990000 frog")); + // Real (infinity) left-padded + format = "fish %11f frog"; + args.clear(); + args.push_back(INFINITY); + output = format.sprintf(args, &error); + REQUIRE(error == false); + CHECK(output == String("fish inf frog")); + // Real right-padded. format = "fish %-11f frog"; args.clear(); @@ -840,6 +848,14 @@ TEST_CASE("[String] sprintf") { REQUIRE(error == false); CHECK(output == String("fish ( 19.990000, 1.000000, -2.050000) frog")); + // Vector left-padded with inf/nan + format = "fish %11v frog"; + args.clear(); + args.push_back(Variant(Vector2(INFINITY, NAN))); + output = format.sprintf(args, &error); + REQUIRE(error == false); + CHECK(output == String("fish ( inf, nan) frog")); + // Vector right-padded. format = "fish %-11v frog"; args.clear(); |