diff options
103 files changed, 1850 insertions, 645 deletions
diff --git a/core/engine.cpp b/core/engine.cpp index 9607dedb3c..50822244cf 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -38,6 +38,7 @@ void Engine::set_iterations_per_second(int p_ips) { + ERR_FAIL_COND(p_ips <= 0); ips = p_ips; } int Engine::get_iterations_per_second() const { diff --git a/core/global_constants.cpp b/core/global_constants.cpp index fb90403226..671b3c545b 100644 --- a/core/global_constants.cpp +++ b/core/global_constants.cpp @@ -425,6 +425,16 @@ void register_global_constants() { BIND_GLOBAL_ENUM_CONSTANT(JOY_DS_X); BIND_GLOBAL_ENUM_CONSTANT(JOY_DS_Y); + BIND_GLOBAL_ENUM_CONSTANT(JOY_VR_GRIP); + BIND_GLOBAL_ENUM_CONSTANT(JOY_VR_PAD); + BIND_GLOBAL_ENUM_CONSTANT(JOY_VR_TRIGGER); + + BIND_GLOBAL_ENUM_CONSTANT(JOY_OCULUS_AX); + BIND_GLOBAL_ENUM_CONSTANT(JOY_OCULUS_BY); + BIND_GLOBAL_ENUM_CONSTANT(JOY_OCULUS_MENU); + + BIND_GLOBAL_ENUM_CONSTANT(JOY_OPENVR_MENU); + BIND_GLOBAL_ENUM_CONSTANT(JOY_SELECT); BIND_GLOBAL_ENUM_CONSTANT(JOY_START); BIND_GLOBAL_ENUM_CONSTANT(JOY_DPAD_UP); @@ -459,6 +469,12 @@ void register_global_constants() { BIND_GLOBAL_ENUM_CONSTANT(JOY_ANALOG_L2); BIND_GLOBAL_ENUM_CONSTANT(JOY_ANALOG_R2); + BIND_GLOBAL_ENUM_CONSTANT(JOY_VR_ANALOG_TRIGGER); + BIND_GLOBAL_ENUM_CONSTANT(JOY_VR_ANALOG_GRIP); + + BIND_GLOBAL_ENUM_CONSTANT(JOY_OPENVR_TOUCHPADX); + BIND_GLOBAL_ENUM_CONSTANT(JOY_OPENVR_TOUCHPADY); + // midi BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_NOTE_OFF); BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_NOTE_ON); diff --git a/core/hash_map.h b/core/hash_map.h index 44459a3080..31332991de 100644 --- a/core/hash_map.h +++ b/core/hash_map.h @@ -162,20 +162,21 @@ private: new_hash_table[i] = 0; } - for (int i = 0; i < (1 << hash_table_power); i++) { + if (hash_table) { + for (int i = 0; i < (1 << hash_table_power); i++) { - while (hash_table[i]) { + while (hash_table[i]) { - Element *se = hash_table[i]; - hash_table[i] = se->next; - int new_pos = se->hash & ((1 << new_hash_table_power) - 1); - se->next = new_hash_table[new_pos]; - new_hash_table[new_pos] = se; + Element *se = hash_table[i]; + hash_table[i] = se->next; + int new_pos = se->hash & ((1 << new_hash_table_power) - 1); + se->next = new_hash_table[new_pos]; + new_hash_table[new_pos] = se; + } } - } - if (hash_table) memdelete_arr(hash_table); + } hash_table = new_hash_table; hash_table_power = new_hash_table_power; } diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index 414742deeb..871e21df3e 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -198,10 +198,6 @@ Error ConfigFile::load(const String &p_path) { section = next_tag.name; } } - - memdelete(f); - - return OK; } void ConfigFile::_bind_methods() { diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 3d71e66f80..0b6e9ae929 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -99,14 +99,22 @@ void AStar::remove_point(int p_id) { Point *p = points[p_id]; - Map<int, Point *>::Element *PE = points.front(); - while (PE) { - for (Set<Point *>::Element *E = PE->get()->neighbours.front(); E; E = E->next()) { - Segment s(p_id, E->get()->id); - segments.erase(s); - E->get()->neighbours.erase(p); - } - PE = PE->next(); + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { + + Segment s(p_id, E->get()->id); + segments.erase(s); + + E->get()->neighbours.erase(p); + E->get()->unlinked_neighbours.erase(p); + } + + for (Set<Point *>::Element *E = p->unlinked_neighbours.front(); E; E = E->next()) { + + Segment s(p_id, E->get()->id); + segments.erase(s); + + E->get()->neighbours.erase(p); + E->get()->unlinked_neighbours.erase(p); } memdelete(p); @@ -125,6 +133,8 @@ void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) { if (bidirectional) b->neighbours.insert(a); + else + b->unlinked_neighbours.insert(a); Segment s(p_id, p_with_id); if (s.from == p_id) { @@ -147,7 +157,9 @@ void AStar::disconnect_points(int p_id, int p_with_id) { Point *a = points[p_id]; Point *b = points[p_with_id]; a->neighbours.erase(b); + a->unlinked_neighbours.erase(b); b->neighbours.erase(a); + b->unlinked_neighbours.erase(a); } bool AStar::has_point(int p_id) const { diff --git a/core/math/a_star.h b/core/math/a_star.h index fac8a9d312..ba35d929b3 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -54,6 +54,7 @@ class AStar : public Reference { bool enabled; Set<Point *> neighbours; + Set<Point *> unlinked_neighbours; // Used for pathfinding Point *prev_point; diff --git a/core/math/random_number_generator.h b/core/math/random_number_generator.h index 6b6bcdd2cd..a6182a4b33 100644 --- a/core/math/random_number_generator.h +++ b/core/math/random_number_generator.h @@ -59,7 +59,10 @@ public: _FORCE_INLINE_ int randi_range(int from, int to) { unsigned int ret = randbase.rand(); - return ret % (to - from + 1) + from; + if (to < from) + return ret % (from - to + 1) + to; + else + return ret % (to - from + 1) + from; } RandomNumberGenerator(); diff --git a/core/math/random_pcg.cpp b/core/math/random_pcg.cpp index 8351bd138e..00c0af515d 100644 --- a/core/math/random_pcg.cpp +++ b/core/math/random_pcg.cpp @@ -43,13 +43,9 @@ void RandomPCG::randomize() { } double RandomPCG::random(double p_from, double p_to) { - unsigned int r = rand(); - double ret = (double)r / (double)RANDOM_MAX; - return (ret) * (p_to - p_from) + p_from; + return randd() * (p_to - p_from) + p_from; } float RandomPCG::random(float p_from, float p_to) { - unsigned int r = rand(); - float ret = (float)r / (float)RANDOM_MAX; - return (ret) * (p_to - p_from) + p_from; + return randf() * (p_to - p_from) + p_from; } diff --git a/core/math/random_pcg.h b/core/math/random_pcg.h index 0d1b311c0d..aa25914638 100644 --- a/core/math/random_pcg.h +++ b/core/math/random_pcg.h @@ -37,6 +37,28 @@ #include "thirdparty/misc/pcg.h" +#if defined(__GNUC__) || (_llvm_has_builtin(__builtin_clz)) +#define CLZ32(x) __builtin_clz(x) +#elif defined(_MSC_VER) +#include "intrin.h" +static int __bsr_clz32(uint32_t x) { + unsigned long index; + _BitScanReverse(&index, x); + return 31 - index; +} +#define CLZ32(x) __bsr_clz32(x) +#else +#endif + +#if defined(__GNUC__) || (_llvm_has_builtin(__builtin_ldexp) && _llvm_has_builtin(__builtin_ldexpf)) +#define LDEXP(s, e) __builtin_ldexp(s, e) +#define LDEXPF(s, e) __builtin_ldexpf(s, e) +#else +#include "math.h" +#define LDEXP(s, e) ldexp(s, e) +#define LDEXPF(s, e) ldexp(s, e) +#endif + class RandomPCG { pcg32_random_t pcg; uint64_t current_seed; // seed with this to get the same state @@ -60,8 +82,44 @@ public: current_seed = pcg.state; return pcg32_random_r(&pcg); } - _FORCE_INLINE_ double randd() { return (double)rand() / (double)RANDOM_MAX; } - _FORCE_INLINE_ float randf() { return (float)rand() / (float)RANDOM_MAX; } + + // Obtaining floating point numbers in [0, 1] range with "good enough" uniformity. + // These functions sample the output of rand() as the fraction part of an infinite binary number, + // with some tricks applied to reduce ops and branching: + // 1. Instead of shifting to the first 1 and connecting random bits, we simply set the MSB and LSB to 1. + // Provided that the RNG is actually uniform bit by bit, this should have the exact same effect. + // 2. In order to compensate for exponent info loss, we count zeros from another random number, + // and just add that to the initial offset. + // This has the same probability as counting and shifting an actual bit stream: 2^-n for n zeroes. + // For all numbers above 2^-96 (2^-64 for floats), the functions should be uniform. + // However, all numbers below that threshold are floored to 0. + // The thresholds are chosen to minimize rand() calls while keeping the numbers within a totally subjective quality standard. + // If clz or ldexp isn't available, fall back to bit truncation for performance, sacrificing uniformity. + _FORCE_INLINE_ double randd() { +#if defined(CLZ32) + uint32_t proto_exp_offset = rand(); + if (unlikely(proto_exp_offset == 0)) { + return 0; + } + uint64_t significand = (((uint64_t)rand()) << 32) | rand() | 0x8000000000000001U; + return LDEXP((double)significand, -64 - CLZ32(proto_exp_offset)); +#else +#pragma message("RandomPCG::randd - intrinsic clz is not available, falling back to bit truncation") + return (double)(((((uint64_t)rand()) << 32) | rand()) & 0x1FFFFFFFFFFFFFU) / (double)0x1FFFFFFFFFFFFFU; +#endif + } + _FORCE_INLINE_ float randf() { +#if defined(CLZ32) + uint32_t proto_exp_offset = rand(); + if (unlikely(proto_exp_offset == 0)) { + return 0; + } + return LDEXPF((float)(rand() | 0x80000001), -32 - CLZ32(proto_exp_offset)); +#else +#pragma message("RandomPCG::randf - intrinsic clz is not available, falling back to bit truncation") + return (float)(rand() & 0xFFFFFF) / (float)0xFFFFFF; +#endif + } _FORCE_INLINE_ double randfn(double p_mean, double p_deviation) { return p_mean + p_deviation * (cos(Math_TAU * randd()) * sqrt(-2.0 * log(randd()))); // Box-Muller transform diff --git a/core/math/vector3.h b/core/math/vector3.h index 6423147282..811a207138 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -224,7 +224,7 @@ Vector3 Vector3::slerp(const Vector3 &p_b, real_t p_t) const { #endif real_t theta = angle_to(p_b); - return rotated(cross(p_b), theta * p_t); + return rotated(cross(p_b).normalized(), theta * p_t); } real_t Vector3::distance_to(const Vector3 &p_b) const { diff --git a/core/os/input_event.h b/core/os/input_event.h index 7a9a1f71c3..2eb321f134 100644 --- a/core/os/input_event.h +++ b/core/os/input_event.h @@ -117,6 +117,16 @@ enum JoystickList { JOY_WII_MINUS = JOY_BUTTON_10, JOY_WII_PLUS = JOY_BUTTON_11, + JOY_VR_GRIP = JOY_BUTTON_2, + JOY_VR_PAD = JOY_BUTTON_14, + JOY_VR_TRIGGER = JOY_BUTTON_15, + + JOY_OCULUS_AX = JOY_BUTTON_7, + JOY_OCULUS_BY = JOY_BUTTON_1, + JOY_OCULUS_MENU = JOY_BUTTON_3, + + JOY_OPENVR_MENU = JOY_BUTTON_1, + // end of history JOY_AXIS_0 = 0, @@ -139,6 +149,12 @@ enum JoystickList { JOY_ANALOG_L2 = JOY_AXIS_6, JOY_ANALOG_R2 = JOY_AXIS_7, + + JOY_VR_ANALOG_TRIGGER = JOY_AXIS_2, + JOY_VR_ANALOG_GRIP = JOY_AXIS_4, + + JOY_OPENVR_TOUCHPADX = JOY_AXIS_0, + JOY_OPENVR_TOUCHPADY = JOY_AXIS_1, }; enum MidiMessageList { diff --git a/core/os/os.h b/core/os/os.h index 4f6a539e78..b128e6424c 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -104,7 +104,6 @@ public: bool maximized; bool always_on_top; bool use_vsync; - bool layered_splash; bool layered; float get_aspect() const { return (float)width / (float)height; } VideoMode(int p_width = 1024, int p_height = 600, bool p_fullscreen = false, bool p_resizable = true, bool p_borderless_window = false, bool p_maximized = false, bool p_always_on_top = false, bool p_use_vsync = false) { @@ -117,7 +116,6 @@ public: always_on_top = p_always_on_top; use_vsync = p_use_vsync; layered = false; - layered_splash = false; } }; diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 76fbd636d1..0508806a35 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -579,10 +579,6 @@ Error ProjectSettings::_load_settings_text(const String p_path) { section = next_tag.name; } } - - memdelete(f); - - return OK; } Error ProjectSettings::_load_settings_text_or_binary(const String p_text_path, const String p_bin_path) { diff --git a/core/ustring.cpp b/core/ustring.cpp index 88b758e883..35b817b1d2 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -2956,26 +2956,12 @@ String String::replace(const char *p_key, const char *p_with) const { String String::replace_first(const String &p_key, const String &p_with) const { - String new_string; - int search_from = 0; - int result = 0; - - while ((result = find(p_key, search_from)) >= 0) { - - new_string += substr(search_from, result - search_from); - new_string += p_with; - search_from = result + p_key.length(); - break; + int pos = find(p_key); + if (pos >= 0) { + return substr(0, pos) + p_with + substr(pos + p_key.length(), length()); } - if (search_from == 0) { - - return *this; - } - - new_string += substr(search_from, length() - search_from); - - return new_string; + return *this; } String String::replacen(const String &p_key, const String &p_with) const { @@ -3235,7 +3221,7 @@ static int _humanize_digits(int p_num) { String String::humanize_size(size_t p_size) { uint64_t _div = 1; - static const char *prefix[] = { " Bytes", " KB", " MB", " GB", "TB", " PB", "HB", "" }; + static const char *prefix[] = { " Bytes", " KB", " MB", " GB", " TB", " PB", " EB", "" }; int prefix_idx = 0; while (p_size > (_div * 1024) && prefix[prefix_idx][0]) { @@ -3246,7 +3232,7 @@ String String::humanize_size(size_t p_size) { int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0; double divisor = prefix_idx > 0 ? _div : 1; - return String::num(p_size / divisor, digits) + prefix[prefix_idx]; + return String::num(p_size / divisor).pad_decimals(digits) + prefix[prefix_idx]; } bool String::is_abs_path() const { diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 760287e1b8..eb612191e7 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1003,6 +1003,27 @@ <constant name="JOY_DS_Y" value="2" enum="JoystickList"> DualShock controller Y button </constant> + <constant name="JOY_VR_GRIP" value="2" enum="JoystickList"> + Grip (side) buttons on a VR controller + </constant> + <constant name="JOY_VR_PAD" value="14" enum="JoystickList"> + Push down on the touchpad or main joystick on a VR controller + </constant> + <constant name="JOY_VR_TRIGGER" value="15" enum="JoystickList"> + Trigger on a VR controller + </constant> + <constant name="JOY_OCULUS_AX" value="7" enum="JoystickList"> + A button on the right Oculus Touch controller, X button on the left controller (also when used in OpenVR) + </constant> + <constant name="JOY_OCULUS_BY" value="1" enum="JoystickList"> + B button on the right Oculus Touch controller, Y button on the left controller (also when used in OpenVR) + </constant> + <constant name="JOY_OCULUS_MENU" value="3" enum="JoystickList"> + Menu button on either Oculus Touch controller. + </constant> + <constant name="JOY_OPENVR_MENU" value="1" enum="JoystickList"> + Menu button in OpenVR (Except when Oculus Touch controllers are used) + </constant> <constant name="JOY_SELECT" value="10" enum="JoystickList"> Joypad Button Select </constant> @@ -1085,6 +1106,18 @@ <constant name="JOY_ANALOG_R2" value="7" enum="JoystickList"> Joypad Right Analog Trigger </constant> + <constant name="JOY_VR_ANALOG_TRIGGER" value="2" enum="JoystickList"> + VR Controller Analog Trigger + </constant> + <constant name="JOY_VR_ANALOG_GRIP" value="4" enum="JoystickList"> + VR Controller Analog Grip (side buttons) + </constant> + <constant name="JOY_OPENVR_TOUCHPADX" value="0" enum="JoystickList"> + OpenVR touchpad X axis (Joystick axis on Oculus Touch and Windows MR controllers) + </constant> + <constant name="JOY_OPENVR_TOUCHPADY" value="1" enum="JoystickList"> + OpenVR touchpad Y axis (Joystick axis on Oculus Touch and Windows MR controllers) + </constant> <constant name="MIDI_MESSAGE_NOTE_OFF" value="8" enum="MidiMessageList"> </constant> <constant name="MIDI_MESSAGE_NOTE_ON" value="9" enum="MidiMessageList"> diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 745d803a30..d44e3c54c9 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ArrayMesh" inherits="Mesh" category="Core" version="3.2"> <brief_description> + [Mesh] type that provides utility for constructing a surface from arrays. </brief_description> <description> The [ArrayMesh] is used to construct a [Mesh] by specifying the attributes as arrays. The most basic example is the creation of a single triangle @@ -30,6 +31,7 @@ <argument index="0" name="name" type="String"> </argument> <description> + Add name for a blend shape that will be added with [method add_surface_from_arrays]. Must be called before surface is added. </description> </method> <method name="add_surface_from_arrays"> @@ -176,6 +178,7 @@ <argument index="2" name="data" type="PoolByteArray"> </argument> <description> + Updates a specified region of mesh arrays on GPU. Warning: only use if you know what you are doing. You can easily cause crashes by calling this function with improper arguments. </description> </method> </methods> @@ -183,7 +186,7 @@ <member name="blend_shape_mode" type="int" setter="set_blend_shape_mode" getter="get_blend_shape_mode" enum="Mesh.BlendShapeMode"> </member> <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb"> - An overriding bounding box for this mesh. + Overrides the [AABB] with one defined by user for use with frustum culling. Especially useful to avoid unnexpected culling when using a shader to offset vertices. </member> </members> <constants> diff --git a/doc/classes/CPUParticles.xml b/doc/classes/CPUParticles.xml index 599c067328..5458a87a9e 100644 --- a/doc/classes/CPUParticles.xml +++ b/doc/classes/CPUParticles.xml @@ -116,6 +116,12 @@ </member> <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> </member> + <member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> + </member> + <member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + </member> + <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + </member> <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> </member> <member name="radial_accel" type="float" setter="set_param" getter="get_param"> @@ -154,30 +160,34 @@ </constant> <constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> </constant> - <constant name="PARAM_LINEAR_ACCEL" value="2" enum="Parameter"> + <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> </constant> - <constant name="PARAM_RADIAL_ACCEL" value="3" enum="Parameter"> + <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> </constant> - <constant name="PARAM_TANGENTIAL_ACCEL" value="4" enum="Parameter"> + <constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> </constant> - <constant name="PARAM_DAMPING" value="5" enum="Parameter"> + <constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> </constant> - <constant name="PARAM_ANGLE" value="6" enum="Parameter"> + <constant name="PARAM_DAMPING" value="6" enum="Parameter"> </constant> - <constant name="PARAM_SCALE" value="7" enum="Parameter"> + <constant name="PARAM_ANGLE" value="7" enum="Parameter"> </constant> - <constant name="PARAM_HUE_VARIATION" value="8" enum="Parameter"> + <constant name="PARAM_SCALE" value="8" enum="Parameter"> </constant> - <constant name="PARAM_ANIM_SPEED" value="9" enum="Parameter"> + <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> </constant> - <constant name="PARAM_ANIM_OFFSET" value="10" enum="Parameter"> + <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> </constant> - <constant name="PARAM_MAX" value="11" enum="Parameter"> + <constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> + </constant> + <constant name="PARAM_MAX" value="12" enum="Parameter"> </constant> <constant name="FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="Flags"> </constant> <constant name="FLAG_ROTATE_Y" value="1" enum="Flags"> </constant> + <constant name="FLAG_DISABLE_Z" value="2" enum="Flags"> + </constant> <constant name="FLAG_MAX" value="3" enum="Flags"> </constant> <constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape"> diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index e1f71e3600..17c68ccb87 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -111,6 +111,12 @@ </member> <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> </member> + <member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> + </member> + <member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + </member> + <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + </member> <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> </member> <member name="radial_accel" type="float" setter="set_param" getter="get_param"> @@ -173,7 +179,11 @@ </constant> <constant name="FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="Flags"> </constant> - <constant name="FLAG_MAX" value="1" enum="Flags"> + <constant name="FLAG_ROTATE_Y" value="1" enum="Flags"> + </constant> + <constant name="FLAG_DISABLE_Z" value="2" enum="Flags"> + </constant> + <constant name="FLAG_MAX" value="3" enum="Flags"> </constant> <constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape"> </constant> diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index ab5d7a0a5d..30e80fa512 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -144,7 +144,7 @@ <return type="Color"> </return> <description> - Returns the inverted color [code](1 - r, 1 - g, 1 - b, 1 - a)[/code]. + Returns the inverted color [code](1 - r, 1 - g, 1 - b, a)[/code]. [codeblock] var c = Color(0.3, 0.4, 0.9) var inverted_color = c.inverted() # a color of an RGBA(178, 153, 26, 255) diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 8dfabdd884..75434b031e 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -361,7 +361,7 @@ <return type="void"> </return> <description> - Locks the data for writing access. + Locks the data for reading and writing access. Sends an error to the console if the image is not locked when reading or writing a pixel. </description> </method> <method name="normalmap_to_xy"> diff --git a/doc/classes/MeshInstance.xml b/doc/classes/MeshInstance.xml index c5c15e0ddc..f5868f51cb 100644 --- a/doc/classes/MeshInstance.xml +++ b/doc/classes/MeshInstance.xml @@ -43,6 +43,7 @@ <return type="int"> </return> <description> + Returns the number of surface materials. </description> </method> <method name="set_surface_material"> diff --git a/doc/classes/MeshInstance2D.xml b/doc/classes/MeshInstance2D.xml index d18ba96a95..39a733fdb3 100644 --- a/doc/classes/MeshInstance2D.xml +++ b/doc/classes/MeshInstance2D.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="MeshInstance2D" inherits="Node2D" category="Core" version="3.2"> <brief_description> + Node used for displaying a [Mesh] in 2D. </brief_description> <description> + Node used for displaying a [Mesh] in 2D. Can be constructed from an existing [Sprite] use tool in Toolbar. Select "Sprite" then "Convert to Mesh2D", select settings in popup and press "Create Mesh2D". </description> <tutorials> <link>http://docs.godotengine.org/en/latest/tutorials/2d/2d_meshes.html</link> @@ -11,10 +13,13 @@ </methods> <members> <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + The [Mesh] that will be drawn by the [MeshInstance2D]. </member> <member name="normal_map" type="Texture" setter="set_normal_map" getter="get_normal_map"> + The normal map that will be used if using the default [CanvasItemMaterial]. </member> <member name="texture" type="Texture" setter="set_texture" getter="get_texture"> + The [Texture] that will be used if using the default [CanvasItemMaterial]. Can be accessed as [code]TEXTURE[/code] in CanvasItem shader. </member> </members> <constants> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index f7cd6c3e83..2592bc6775 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -818,6 +818,7 @@ </member> <member name="window_borderless" type="bool" setter="set_borderless_window" getter="get_borderless_window"> If [code]true[/code], removes the window frame. + Note: Setting [code]window_borderless[/code] to [code]false[/code] disables per-pixel transparency. </member> <member name="window_fullscreen" type="bool" setter="set_window_fullscreen" getter="is_window_fullscreen"> If [code]true[/code], the window is fullscreen. @@ -829,6 +830,9 @@ If [code]true[/code], the window is minimized. </member> <member name="window_per_pixel_transparency_enabled" type="bool" setter="set_window_per_pixel_transparency_enabled" getter="get_window_per_pixel_transparency_enabled"> + If [code]true[/code], the window background is transparent and window frame is removed. + Use [code]get_tree().get_root().set_transparent_background(true)[/code] to disable main viewport background rendering. + Note: This property has no effect if "Project > Project Settings > Display > Window > Per-pixel transparency > Allowed" setting is disabled. </member> <member name="window_position" type="Vector2" setter="set_window_position" getter="get_window_position"> The window position relative to the screen, the origin is the top left corner, +Y axis goes to the bottom and +X axis goes to the right. diff --git a/doc/classes/PrimitiveMesh.xml b/doc/classes/PrimitiveMesh.xml index c9c3643edb..2214357308 100644 --- a/doc/classes/PrimitiveMesh.xml +++ b/doc/classes/PrimitiveMesh.xml @@ -4,7 +4,7 @@ Base class for all primitive meshes. Handles applying a [Material] to a primitive mesh. </brief_description> <description> - Base class for all primitive meshes. Handles applying a [Material] to a primitive mesh. + Base class for all primitive meshes. Handles applying a [Material] to a primitive mesh. Examples include [CapsuleMesh], [CubeMesh], [CylinderMesh], [PlaneMesh], [PrismMesh], [QuadMesh], and [SphereMesh]. </description> <tutorials> </tutorials> @@ -13,13 +13,16 @@ <return type="Array"> </return> <description> + Returns mesh arrays used to constitute surface of [Mesh]. Mesh array can be used with [ArrayMesh] to create new surface. </description> </method> </methods> <members> <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb"> + Overrides the [AABB] with one defined by user for use with frustum culling. Especially useful to avoid unnexpected culling when using a shader to offset vertices. </member> <member name="flip_faces" type="bool" setter="set_flip_faces" getter="get_flip_faces"> + If set, the order of the vertices in each triangle are reversed resulting in the backside of the mesh being drawn. Result is the same as using *CULL_BACK* in [SpatialMaterial]. Default is false. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The current [Material] of the primitive mesh. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 460db5e792..196fcbfaaa 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -374,11 +374,10 @@ Default orientation on mobile devices. </member> <member name="display/window/per_pixel_transparency/allowed" type="bool" setter="" getter=""> - If [code]true[/code], allows per-pixel transparency in a desktop window. This affects performance if not needed, so leave it on [code]false[/code] unless you need it. + If [code]true[/code], allows per-pixel transparency in a desktop window. This affects performance, so leave it on [code]false[/code] unless you need it. </member> <member name="display/window/per_pixel_transparency/enabled" type="bool" setter="" getter=""> - </member> - <member name="display/window/per_pixel_transparency/splash" type="bool" setter="" getter=""> + Set the window background to transparent when it starts. </member> <member name="display/window/size/always_on_top" type="bool" setter="" getter=""> Force the window to be always on top. diff --git a/doc/classes/QuadMesh.xml b/doc/classes/QuadMesh.xml index 779ce11180..cf7e56f895 100644 --- a/doc/classes/QuadMesh.xml +++ b/doc/classes/QuadMesh.xml @@ -12,6 +12,7 @@ </methods> <members> <member name="size" type="Vector2" setter="set_size" getter="get_size"> + Size in the X and Y axes. Default is [code]Vector2(1, 1)[/code]. </member> </members> <constants> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 62e8a2c34f..8ca553ccb8 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -391,7 +391,7 @@ <member name="fold_gutter" type="bool" setter="set_draw_fold_gutter" getter="is_drawing_fold_gutter"> If [code]true[/code], the fold gutter is visible. This enables folding groups of indented lines. </member> - <member name="hiding_enabled" type="int" setter="set_hiding_enabled" getter="is_hiding_enabled"> + <member name="hiding_enabled" type="bool" setter="set_hiding_enabled" getter="is_hiding_enabled"> </member> <member name="highlight_all_occurrences" type="bool" setter="set_highlight_all_occurrences" getter="is_highlight_all_occurrences_enabled"> </member> diff --git a/doc/classes/TriangleMesh.xml b/doc/classes/TriangleMesh.xml index 9600a1d196..2125aa5e17 100644 --- a/doc/classes/TriangleMesh.xml +++ b/doc/classes/TriangleMesh.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TriangleMesh" inherits="Reference" category="Core" version="3.2"> <brief_description> + Internal mesh type. </brief_description> <description> + Mesh type used internally for collision calculations. </description> <tutorials> </tutorials> diff --git a/doc/classes/VehicleWheel.xml b/doc/classes/VehicleWheel.xml index c3b668c170..0bc0e351e4 100644 --- a/doc/classes/VehicleWheel.xml +++ b/doc/classes/VehicleWheel.xml @@ -19,7 +19,7 @@ <return type="float"> </return> <description> - Returns a value between 0.0 and 1.0 that indicates whether this wheel is skidding. 0.0 is not skidding, 1.0 means the wheel has lost grip. + Returns a value between 0.0 and 1.0 that indicates whether this wheel is skidding. 0.0 is skidding (the wheel has lost grip, e.g. icy terrain), 1.0 means not skidding (the wheel has full grip, e.g. dry asphalt road). </description> </method> <method name="is_in_contact" qualifiers="const"> diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index 35694f000c..0231bb5837 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -2261,7 +2261,7 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements, bool rebind_reflection = false; bool rebind_lightmap = false; - if (!p_shadow) { + if (!p_shadow && material->shader) { bool unshaded = material->shader->spatial.unshaded; @@ -2281,7 +2281,7 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements, bool depth_prepass = false; - if (!p_alpha_pass && material->shader && material->shader->spatial.depth_draw_mode == RasterizerStorageGLES2::Shader::Spatial::DEPTH_DRAW_ALPHA_PREPASS) { + if (!p_alpha_pass && material->shader->spatial.depth_draw_mode == RasterizerStorageGLES2::Shader::Spatial::DEPTH_DRAW_ALPHA_PREPASS) { depth_prepass = true; } @@ -2919,7 +2919,7 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const if (storage->frame.current_rt && state.used_screen_texture) { //copy screen texture - if (storage->frame.current_rt && storage->frame.current_rt->multisample_active) { + if (storage->frame.current_rt->multisample_active) { // Resolve framebuffer to front buffer before copying #ifdef GLES_OVER_GL diff --git a/drivers/gles2/shader_compiler_gles2.cpp b/drivers/gles2/shader_compiler_gles2.cpp index 9778d39a21..b48b93944c 100644 --- a/drivers/gles2/shader_compiler_gles2.cpp +++ b/drivers/gles2/shader_compiler_gles2.cpp @@ -361,6 +361,21 @@ String ShaderCompilerGLES2::_dump_node_code(SL::Node *p_node, int p_level, Gener fragment_global += final_code; } + // constants + + for (Map<StringName, SL::ShaderNode::Constant>::Element *E = snode->constants.front(); E; E = E->next()) { + String gcode; + gcode += "const "; + gcode += _prestr(E->get().precision); + gcode += _typestr(E->get().type); + gcode += " " + _mkid(E->key()); + gcode += "="; + gcode += _dump_node_code(E->get().initializer, p_level, r_gen_code, p_actions, p_default_actions, p_assigning); + gcode += ";\n"; + vertex_global += gcode; + fragment_global += gcode; + } + // functions Map<StringName, String> function_code; diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index db7e337b9f..7e02f2f3f5 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -4552,8 +4552,8 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const } _post_process(env, p_cam_projection); - - if (false && shadow_atlas) { + // Needed only for debugging + /* if (shadow_atlas && storage->frame.current_rt) { //_copy_texture_to_front_buffer(shadow_atlas->depth); storage->canvas->canvas_begin(); @@ -4563,7 +4563,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const storage->canvas->draw_generic_textured_rect(Rect2(0, 0, storage->frame.current_rt->width / 2, storage->frame.current_rt->height / 2), Rect2(0, 0, 1, 1)); } - if (false && storage->frame.current_rt) { + if (storage->frame.current_rt) { //_copy_texture_to_front_buffer(shadow_atlas->depth); storage->canvas->canvas_begin(); @@ -4573,7 +4573,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const storage->canvas->draw_generic_textured_rect(Rect2(0, 0, storage->frame.current_rt->width / 16, storage->frame.current_rt->height / 16), Rect2(0, 0, 1, 1)); } - if (false && reflection_atlas && storage->frame.current_rt) { + if (reflection_atlas && storage->frame.current_rt) { //_copy_texture_to_front_buffer(shadow_atlas->depth); storage->canvas->canvas_begin(); @@ -4582,7 +4582,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const storage->canvas->draw_generic_textured_rect(Rect2(0, 0, storage->frame.current_rt->width / 2, storage->frame.current_rt->height / 2), Rect2(0, 0, 1, 1)); } - if (false && directional_shadow.fbo) { + if (directional_shadow.fbo) { //_copy_texture_to_front_buffer(shadow_atlas->depth); storage->canvas->canvas_begin(); @@ -4592,7 +4592,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const storage->canvas->draw_generic_textured_rect(Rect2(0, 0, storage->frame.current_rt->width / 2, storage->frame.current_rt->height / 2), Rect2(0, 0, 1, 1)); } - if (false && env_radiance_tex) { + if ( env_radiance_tex) { //_copy_texture_to_front_buffer(shadow_atlas->depth); storage->canvas->canvas_begin(); @@ -4603,8 +4603,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const storage->canvas->draw_generic_textured_rect(Rect2(0, 0, storage->frame.current_rt->width / 2, storage->frame.current_rt->height / 2), Rect2(0, 0, 1, 1)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - + }*/ //disable all stuff } diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 561a3cae2d..01b85458c2 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -3501,7 +3501,7 @@ void RasterizerStorageGLES3::mesh_add_surface(RID p_mesh, uint32_t p_format, VS: if (p_vertex_count < (1 << 16)) { //read 16 bit indices const uint16_t *src_idx = (const uint16_t *)ir.ptr(); - for (int i = 0; i < index_count; i += 6) { + for (int i = 0; i + 5 < index_count; i += 6) { wr[i + 0] = src_idx[i / 2]; wr[i + 1] = src_idx[i / 2 + 1]; @@ -3515,7 +3515,7 @@ void RasterizerStorageGLES3::mesh_add_surface(RID p_mesh, uint32_t p_format, VS: //read 16 bit indices const uint32_t *src_idx = (const uint32_t *)ir.ptr(); - for (int i = 0; i < index_count; i += 6) { + for (int i = 0; i + 5 < index_count; i += 6) { wr[i + 0] = src_idx[i / 2]; wr[i + 1] = src_idx[i / 2 + 1]; @@ -3531,7 +3531,7 @@ void RasterizerStorageGLES3::mesh_add_surface(RID p_mesh, uint32_t p_format, VS: index_count = p_vertex_count * 2; wf_indices.resize(index_count); PoolVector<uint32_t>::Write wr = wf_indices.write(); - for (int i = 0; i < index_count; i += 6) { + for (int i = 0; i + 5 < index_count; i += 6) { wr[i + 0] = i / 2; wr[i + 1] = i / 2 + 1; diff --git a/drivers/gles3/shader_compiler_gles3.cpp b/drivers/gles3/shader_compiler_gles3.cpp index e8417900ea..b0f0a71d56 100644 --- a/drivers/gles3/shader_compiler_gles3.cpp +++ b/drivers/gles3/shader_compiler_gles3.cpp @@ -472,6 +472,19 @@ String ShaderCompilerGLES3::_dump_node_code(SL::Node *p_node, int p_level, Gener r_gen_code.fragment_global += interp_mode + "in " + vcode; } + for (Map<StringName, SL::ShaderNode::Constant>::Element *E = pnode->constants.front(); E; E = E->next()) { + String gcode; + gcode += "const "; + gcode += _prestr(E->get().precision); + gcode += _typestr(E->get().type); + gcode += " " + _mkid(E->key()); + gcode += "="; + gcode += _dump_node_code(E->get().initializer, p_level, r_gen_code, p_actions, p_default_actions, p_assigning); + gcode += ";\n"; + r_gen_code.vertex_global += gcode; + r_gen_code.fragment_global += gcode; + } + Map<StringName, String> function_code; //code for functions diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 7dadbf88fb..0385220baa 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1214,7 +1214,7 @@ void AnimationTrackEdit::_notification(int p_what) { Color accent = get_color("accent_color", "Editor"); accent.a *= 0.7; // Offside so the horizontal sides aren't cutoff. - draw_rect(Rect2(Point2(1, 0), get_size() - Size2(1, 0)), accent, false); + draw_rect(Rect2(Point2(1 * EDSCALE, 0), get_size() - Size2(1 * EDSCALE, 0)), accent, false); } Ref<Font> font = get_font("font", "Label"); diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp index 85db8b77f9..b30b94ab26 100644 --- a/editor/audio_stream_preview.cpp +++ b/editor/audio_stream_preview.cpp @@ -129,7 +129,7 @@ void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) { float max = -1000; float min = 1000; int from = uint64_t(i) * to_read / to_write; - int to = uint64_t(i + 1) * to_read / to_write; + int to = (uint64_t(i) + 1) * to_read / to_write; to = MIN(to, to_read); from = MIN(from, to_read - 1); if (to == from) { diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 4a440510db..01773a0bcd 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -930,7 +930,7 @@ void CodeTextEditor::convert_case(CaseStyle p_case) { for (int i = begin; i <= end; i++) { int len = text_editor->get_line(i).length(); if (i == end) - len -= len - end_col; + len = end_col; if (i == begin) len -= begin_col; String new_line = text_editor->get_line(i).substr(i == begin ? begin_col : 0, len); diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 82fc8e5cfa..6d3603f31b 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -126,7 +126,6 @@ void ConnectDialog::ok_pressed() { } } emit_signal("connected"); - hide(); } void ConnectDialog::_cancel_pressed() { @@ -364,7 +363,7 @@ ConnectDialog::ConnectDialog() { tree->connect("node_selected", this, "_tree_node_selected"); tree->set_connect_to_script_mode(true); - Node *mc = vbc_left->add_margin_child(TTR("Connect To Script:"), tree, true); + Node *mc = vbc_left->add_margin_child(TTR("Connect to Script:"), tree, true); connect_to_label = Object::cast_to<Label>(vbc_left->get_child(mc->get_index() - 1)); error_label = memnew(Label); @@ -414,7 +413,7 @@ ConnectDialog::ConnectDialog() { vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true); HBoxContainer *dstm_hb = memnew(HBoxContainer); - vbc_left->add_margin_child("Method to Create:", dstm_hb); + vbc_left->add_margin_child("Receiver Method:", dstm_hb); dst_method = memnew(LineEdit); dst_method->set_h_size_flags(SIZE_EXPAND_FILL); @@ -458,7 +457,7 @@ ConnectDialog::~ConnectDialog() { memdelete(cdbinds); } -//ConnectionsDock ========================== +////////////////////////////////////////// struct _ConnectionsDockMethodInfoSort { @@ -490,11 +489,29 @@ void ConnectionsDock::_make_or_edit_connection() { bool oshot = connect_dialog->get_oneshot(); cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); - //conditions to add function, must have a script and must have a method - bool add_script_function = !target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method); + // Conditions to add function: must have a script and must not have the method already + // (in the class, the script itself, or inherited). + bool add_script_function = false; + Ref<Script> script = target->get_script(); + if (!target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method)) { + // There is a chance that the method is inherited from another script. + bool found_inherited_function = false; + Ref<Script> inherited_script = script->get_base_script(); + while (!inherited_script.is_null()) { + int line = inherited_script->get_language()->find_function(cToMake.method, inherited_script->get_source_code()); + if (line != -1) { + found_inherited_function = true; + break; + } + + inherited_script = inherited_script->get_base_script(); + } + + add_script_function = !found_inherited_function; + } PoolStringArray script_function_args; if (add_script_function) { - // pick up args here before "it" is deleted by update_tree + // Pick up args here before "it" is deleted by update_tree. script_function_args = it->get_metadata(0).operator Dictionary()["args"]; for (int i = 0; i < cToMake.binds.size(); i++) { script_function_args.append("extra_arg_" + itos(i)); @@ -508,8 +525,7 @@ void ConnectionsDock::_make_or_edit_connection() { _connect(cToMake); } - // IMPORTANT NOTE: _disconnect and _connect cause an update_tree, - // which will delete the object "it" is pointing to + // IMPORTANT NOTE: _disconnect and _connect cause an update_tree, which will delete the object "it" is pointing to. it = NULL; if (add_script_function) { @@ -549,7 +565,7 @@ Break single connection w/ undo-redo functionality. void ConnectionsDock::_disconnect(TreeItem &item) { Connection c = item.get_metadata(0); - ERR_FAIL_COND(c.source != selectedNode); //shouldn't happen but...bugcheck + ERR_FAIL_COND(c.source != selectedNode); // Shouldn't happen but... Bugcheck. undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), c.signal, c.method)); @@ -557,7 +573,7 @@ void ConnectionsDock::_disconnect(TreeItem &item) { undo_redo->add_undo_method(selectedNode, "connect", c.signal, c.target, c.method, c.binds, c.flags); undo_redo->add_do_method(this, "update_tree"); undo_redo->add_undo_method(this, "update_tree"); - undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree + undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); // To force redraw of scene tree. undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); undo_redo->commit_action(); @@ -596,7 +612,7 @@ void ConnectionsDock::_disconnect_all() { void ConnectionsDock::_tree_item_selected() { TreeItem *item = tree->get_selected(); - if (!item) { //Unlikely. Disable button just in case. + if (!item) { // Unlikely. Disable button just in case. connect_button->set_text(TTR("Connect...")); connect_button->set_disabled(true); } else if (_is_item_signal(*item)) { @@ -608,7 +624,7 @@ void ConnectionsDock::_tree_item_selected() { } } -void ConnectionsDock::_tree_item_activated() { //"Activation" on double-click. +void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click. TreeItem *item = tree->get_selected(); @@ -630,7 +646,6 @@ bool ConnectionsDock::_is_item_signal(TreeItem &item) { /* Open connection dialog with TreeItem data to CREATE a brand-new connection. */ - void ConnectionsDock::_open_connection_dialog(TreeItem &item) { String signal = item.get_metadata(0).operator Dictionary()["name"]; @@ -640,10 +655,10 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &item) { CharType c = midname[i]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { if (c == ' ') { - //Replace spaces with underlines. + // Replace spaces with underlines. c = '_'; } else { - //Remove any other characters. + // Remove any other characters. midname.remove(i); i--; continue; @@ -679,7 +694,7 @@ void ConnectionsDock::_open_connection_dialog(Connection cToEdit) { if (src && dst) { connect_dialog->set_title(TTR("Edit Connection:") + cToEdit.signal); - connect_dialog->popup_centered_ratio(); + connect_dialog->popup_centered(); connect_dialog->init(cToEdit, true); } } @@ -828,7 +843,6 @@ void ConnectionsDock::update_tree() { selectedNode->get_signal_list(&node_signals); - //node_signals.sort_custom<_ConnectionsDockMethodInfoSort>(); bool did_script = false; StringName base = selectedNode->get_class(); @@ -955,7 +969,7 @@ void ConnectionsDock::update_tree() { } } - connect_button->set_text(TTR("Connect")); + connect_button->set_text(TTR("Connect...")); connect_button->set_disabled(true); } @@ -975,7 +989,6 @@ ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { tree->set_allow_rmb_select(true); connect_button = memnew(Button); - connect_button->set_text(TTR("Connect")); HBoxContainer *hb = memnew(HBoxContainer); vbc->add_child(hb); hb->add_spacer(); @@ -1005,15 +1018,6 @@ ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { slot_menu->add_item(TTR("Go To Method"), GO_TO_SCRIPT); slot_menu->add_item(TTR("Disconnect"), DISCONNECT); - /* - node_only->set_anchor( MARGIN_TOP, ANCHOR_END ); - node_only->set_anchor( MARGIN_BOTTOM, ANCHOR_END ); - node_only->set_anchor( MARGIN_RIGHT, ANCHOR_END ); - - node_only->set_begin( Point2( 20,51) ); - node_only->set_end( Point2( 10,44) ); - */ - connect_dialog->connect("connected", this, "_make_or_edit_connection"); tree->connect("item_selected", this, "_tree_item_selected"); tree->connect("item_activated", this, "_tree_item_activated"); diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 94f1510810..195c9e1e7d 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -104,7 +104,7 @@ public: ~ConnectDialog(); }; -//======================================== +////////////////////////////////////////// class ConnectionsDock : public VBoxContainer { diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 604a050fcd..e84602b29f 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -246,13 +246,14 @@ bool CreateDialog::_is_class_disabled_by_feature_profile(const StringName &p_cla if (profile->is_class_disabled(class_name)) { return true; } - class_name = ClassDB::get_parent_class(class_name); + class_name = ClassDB::get_parent_class_nocheck(class_name); } return false; } void CreateDialog::select_type(const String &p_type) { + TreeItem *to_select; if (search_options_types.has(p_type)) { to_select = search_options_types[p_type]; @@ -279,10 +280,6 @@ void CreateDialog::_update_search() { favorite->set_disabled(true); help_bit->set_text(""); - /* - TreeItem *root = search_options->create_item(); - _parse_fs(EditorFileSystem::get_singleton()->get_filesystem()); -*/ search_options_types.clear(); @@ -733,6 +730,7 @@ CreateDialog::CreateDialog() { fav_vb->add_margin_child(TTR("Favorites:"), favorites, true); favorites->set_hide_root(true); favorites->set_hide_folding(true); + favorites->set_allow_reselect(true); favorites->connect("cell_selected", this, "_favorite_selected"); favorites->connect("item_activated", this, "_favorite_activated"); favorites->set_drag_forwarding(this); @@ -747,6 +745,7 @@ CreateDialog::CreateDialog() { rec_vb->add_margin_child(TTR("Recent:"), recent, true); recent->set_hide_root(true); recent->set_hide_folding(true); + recent->set_allow_reselect(true); recent->connect("cell_selected", this, "_history_selected"); recent->connect("item_activated", this, "_history_activated"); recent->add_constant_override("draw_guides", 1); diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 9cd7d781a4..57fac241b0 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -63,118 +63,132 @@ void EditorAudioBus::_update_visible_channels() { void EditorAudioBus::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - - for (int i = 0; i < CHANNELS_MAX; i++) { - channel[i].vu_l->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); - channel[i].vu_l->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); - channel[i].vu_r->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); - channel[i].vu_r->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); - channel[i].prev_active = true; - } - - disabled_vu = get_icon("BusVuFrozen", "EditorIcons"); - - Color solo_color = Color::html(EditorSettings::get_singleton()->is_dark_theme() ? "#ffe337" : "#ffeb70"); - Color mute_color = Color::html(EditorSettings::get_singleton()->is_dark_theme() ? "#ff2929" : "#ff7070"); - Color bypass_color = Color::html(EditorSettings::get_singleton()->is_dark_theme() ? "#22ccff" : "#70deff"); - - solo->set_icon(get_icon("AudioBusSolo", "EditorIcons")); - solo->add_color_override("icon_color_pressed", solo_color); - mute->set_icon(get_icon("AudioBusMute", "EditorIcons")); - mute->add_color_override("icon_color_pressed", mute_color); - bypass->set_icon(get_icon("AudioBusBypass", "EditorIcons")); - bypass->add_color_override("icon_color_pressed", bypass_color); - - bus_options->set_icon(get_icon("GuiMiniTabMenu", "EditorIcons")); - - update_bus(); - set_process(true); - } + switch (p_what) { + case NOTIFICATION_READY: { + + for (int i = 0; i < CHANNELS_MAX; i++) { + channel[i].vu_l->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); + channel[i].vu_l->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); + channel[i].vu_r->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); + channel[i].vu_r->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); + channel[i].prev_active = true; + } - if (p_what == NOTIFICATION_DRAW) { + disabled_vu = get_icon("BusVuFrozen", "EditorIcons"); - if (has_focus()) { - draw_style_box(get_stylebox("focus", "Button"), Rect2(Vector2(), get_size())); - } else if (is_master) { - draw_style_box(get_stylebox("disabled", "Button"), Rect2(Vector2(), get_size())); - } - } + Color solo_color = Color::html(EditorSettings::get_singleton()->is_dark_theme() ? "#ffe337" : "#ffeb70"); + Color mute_color = Color::html(EditorSettings::get_singleton()->is_dark_theme() ? "#ff2929" : "#ff7070"); + Color bypass_color = Color::html(EditorSettings::get_singleton()->is_dark_theme() ? "#22ccff" : "#70deff"); - if (p_what == NOTIFICATION_PROCESS) { + solo->set_icon(get_icon("AudioBusSolo", "EditorIcons")); + solo->add_color_override("icon_color_pressed", solo_color); + mute->set_icon(get_icon("AudioBusMute", "EditorIcons")); + mute->add_color_override("icon_color_pressed", mute_color); + bypass->set_icon(get_icon("AudioBusBypass", "EditorIcons")); + bypass->add_color_override("icon_color_pressed", bypass_color); - if (cc != AudioServer::get_singleton()->get_bus_channels(get_index())) { - cc = AudioServer::get_singleton()->get_bus_channels(get_index()); - _update_visible_channels(); - } + bus_options->set_icon(get_icon("GuiMiniTabMenu", "EditorIcons")); - for (int i = 0; i < cc; i++) { - float real_peak[2] = { -100, -100 }; - bool activity_found = false; + update_bus(); + set_process(true); + } break; + case NOTIFICATION_DRAW: { - if (AudioServer::get_singleton()->is_bus_channel_active(get_index(), i)) { - activity_found = true; - real_peak[0] = MAX(real_peak[0], AudioServer::get_singleton()->get_bus_peak_volume_left_db(get_index(), i)); - real_peak[1] = MAX(real_peak[1], AudioServer::get_singleton()->get_bus_peak_volume_right_db(get_index(), i)); + if (is_master) { + draw_style_box(get_stylebox("disabled", "Button"), Rect2(Vector2(), get_size())); + } else if (has_focus()) { + draw_style_box(get_stylebox("focus", "Button"), Rect2(Vector2(), get_size())); + } else { + draw_style_box(get_stylebox("panel", "TabContainer"), Rect2(Vector2(), get_size())); } - if (real_peak[0] > channel[i].peak_l) { - channel[i].peak_l = real_peak[0]; - } else { - channel[i].peak_l -= get_process_delta_time() * 60.0; + if (get_index() != 0 && hovering_drop) { + Color accent = get_color("accent_color", "Editor"); + accent.a *= 0.7; + draw_rect(Rect2(Point2(), get_size()), accent, false); } + } break; + case NOTIFICATION_PROCESS: { - if (real_peak[1] > channel[i].peak_r) { - channel[i].peak_r = real_peak[1]; - } else { - channel[i].peak_r -= get_process_delta_time() * 60.0; + if (cc != AudioServer::get_singleton()->get_bus_channels(get_index())) { + cc = AudioServer::get_singleton()->get_bus_channels(get_index()); + _update_visible_channels(); } - channel[i].vu_l->set_value(channel[i].peak_l); - channel[i].vu_r->set_value(channel[i].peak_r); + for (int i = 0; i < cc; i++) { + float real_peak[2] = { -100, -100 }; + bool activity_found = false; - if (activity_found != channel[i].prev_active) { - if (activity_found) { - channel[i].vu_l->set_over_texture(Ref<Texture>()); - channel[i].vu_r->set_over_texture(Ref<Texture>()); + if (AudioServer::get_singleton()->is_bus_channel_active(get_index(), i)) { + activity_found = true; + real_peak[0] = MAX(real_peak[0], AudioServer::get_singleton()->get_bus_peak_volume_left_db(get_index(), i)); + real_peak[1] = MAX(real_peak[1], AudioServer::get_singleton()->get_bus_peak_volume_right_db(get_index(), i)); + } + + if (real_peak[0] > channel[i].peak_l) { + channel[i].peak_l = real_peak[0]; } else { - channel[i].vu_l->set_over_texture(disabled_vu); - channel[i].vu_r->set_over_texture(disabled_vu); + channel[i].peak_l -= get_process_delta_time() * 60.0; } - channel[i].prev_active = activity_found; - } - } - } + if (real_peak[1] > channel[i].peak_r) { + channel[i].peak_r = real_peak[1]; + } else { + channel[i].peak_r -= get_process_delta_time() * 60.0; + } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + channel[i].vu_l->set_value(channel[i].peak_l); + channel[i].vu_r->set_value(channel[i].peak_r); - for (int i = 0; i < CHANNELS_MAX; i++) { - channel[i].peak_l = -100; - channel[i].peak_r = -100; - channel[i].prev_active = true; - } + if (activity_found != channel[i].prev_active) { + if (activity_found) { + channel[i].vu_l->set_over_texture(Ref<Texture>()); + channel[i].vu_r->set_over_texture(Ref<Texture>()); + } else { + channel[i].vu_l->set_over_texture(disabled_vu); + channel[i].vu_r->set_over_texture(disabled_vu); + } - set_process(is_visible_in_tree()); - } + channel[i].prev_active = activity_found; + } + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { - if (p_what == NOTIFICATION_THEME_CHANGED) { + for (int i = 0; i < CHANNELS_MAX; i++) { + channel[i].peak_l = -100; + channel[i].peak_r = -100; + channel[i].prev_active = true; + } - for (int i = 0; i < CHANNELS_MAX; i++) { - channel[i].vu_l->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); - channel[i].vu_l->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); - channel[i].vu_r->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); - channel[i].vu_r->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); - channel[i].prev_active = true; - } + set_process(is_visible_in_tree()); + } break; + case NOTIFICATION_THEME_CHANGED: { - disabled_vu = get_icon("BusVuFrozen", "EditorIcons"); + for (int i = 0; i < CHANNELS_MAX; i++) { + channel[i].vu_l->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); + channel[i].vu_l->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); + channel[i].vu_r->set_under_texture(get_icon("BusVuEmpty", "EditorIcons")); + channel[i].vu_r->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); + channel[i].prev_active = true; + } - solo->set_icon(get_icon("AudioBusSolo", "EditorIcons")); - mute->set_icon(get_icon("AudioBusMute", "EditorIcons")); - bypass->set_icon(get_icon("AudioBusBypass", "EditorIcons")); + disabled_vu = get_icon("BusVuFrozen", "EditorIcons"); - bus_options->set_icon(get_icon("GuiMiniTabMenu", "EditorIcons")); + solo->set_icon(get_icon("AudioBusSolo", "EditorIcons")); + mute->set_icon(get_icon("AudioBusMute", "EditorIcons")); + bypass->set_icon(get_icon("AudioBusBypass", "EditorIcons")); + + bus_options->set_icon(get_icon("GuiMiniTabMenu", "EditorIcons")); + } break; + case NOTIFICATION_MOUSE_EXIT: + case NOTIFICATION_DRAG_END: { + + if (hovering_drop) { + hovering_drop = false; + update(); + } + } break; } } @@ -553,6 +567,7 @@ Variant EditorAudioBus::get_drag_data(const Point2 &p_point) { Control *c = memnew(Control); Panel *p = memnew(Panel); c->add_child(p); + p->set_modulate(Color(1, 1, 1, 0.7)); p->add_style_override("panel", get_stylebox("focus", "Button")); p->set_size(get_size()); p->set_position(-p_point); @@ -560,21 +575,29 @@ Variant EditorAudioBus::get_drag_data(const Point2 &p_point) { Dictionary d; d["type"] = "move_audio_bus"; d["index"] = get_index(); - emit_signal("drop_end_request"); + + if (get_index() < AudioServer::get_singleton()->get_bus_count() - 1) { + emit_signal("drop_end_request"); + } + return d; } bool EditorAudioBus::can_drop_data(const Point2 &p_point, const Variant &p_data) const { - if (get_index() == 0) + if (get_index() == 0) { return false; + } + Dictionary d = p_data; - if (d.has("type") && String(d["type"]) == "move_audio_bus") { + if (d.has("type") && String(d["type"]) == "move_audio_bus" && (int)d["index"] != get_index()) { + hovering_drop = true; return true; } return false; } + void EditorAudioBus::drop_data(const Point2 &p_point, const Variant &p_data) { Dictionary d = p_data; @@ -589,7 +612,6 @@ Variant EditorAudioBus::get_drag_data_fw(const Point2 &p_point, Control *p_from) } Variant md = item->get_metadata(0); - if (md.get_type() == Variant::INT) { Dictionary fxd; fxd["type"] = "audio_bus_effect"; @@ -749,6 +771,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { buses = p_buses; updating_bus = false; is_master = p_is_master; + hovering_drop = false; set_tooltip(TTR("Audio Bus, Drag and Drop to rearrange.")); @@ -756,7 +779,6 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { add_child(vb); set_v_size_flags(SIZE_EXPAND_FILL); - set_custom_minimum_size(Size2(110, 0) * EDSCALE); track_name = memnew(LineEdit); track_name->connect("text_entered", this, "_name_changed"); @@ -800,7 +822,9 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { child->add_style_override("pressed", sbempty); } - vb->add_child(memnew(HSeparator)); + HSeparator *separator = memnew(HSeparator); + separator->set_mouse_filter(MOUSE_FILTER_PASS); + vb->add_child(separator); HBoxContainer *hb = memnew(HBoxContainer); vb->add_child(hb); @@ -811,20 +835,19 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { slider->set_clip_contents(false); audio_value_preview_box = memnew(Panel); - { - HBoxContainer *audioprev_hbc = memnew(HBoxContainer); - audioprev_hbc->set_v_size_flags(SIZE_EXPAND_FILL); - audioprev_hbc->set_h_size_flags(SIZE_EXPAND_FILL); - audioprev_hbc->set_mouse_filter(MOUSE_FILTER_PASS); - audio_value_preview_box->add_child(audioprev_hbc); - - audio_value_preview_label = memnew(Label); - audio_value_preview_label->set_v_size_flags(SIZE_EXPAND_FILL); - audio_value_preview_label->set_h_size_flags(SIZE_EXPAND_FILL); - audio_value_preview_label->set_mouse_filter(MOUSE_FILTER_PASS); - - audioprev_hbc->add_child(audio_value_preview_label); - } + HBoxContainer *audioprev_hbc = memnew(HBoxContainer); + audioprev_hbc->set_v_size_flags(SIZE_EXPAND_FILL); + audioprev_hbc->set_h_size_flags(SIZE_EXPAND_FILL); + audioprev_hbc->set_mouse_filter(MOUSE_FILTER_PASS); + audio_value_preview_box->add_child(audioprev_hbc); + + audio_value_preview_label = memnew(Label); + audio_value_preview_label->set_v_size_flags(SIZE_EXPAND_FILL); + audio_value_preview_label->set_h_size_flags(SIZE_EXPAND_FILL); + audio_value_preview_label->set_mouse_filter(MOUSE_FILTER_PASS); + + audioprev_hbc->add_child(audio_value_preview_label); + slider->add_child(audio_value_preview_box); audio_value_preview_box->set_as_toplevel(true); Ref<StyleBoxFlat> panel_style = memnew(StyleBoxFlat); @@ -863,17 +886,18 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { channel[i].peak_r = 0.0f; } - scale = memnew(EditorAudioMeterNotches); + EditorAudioMeterNotches *scale = memnew(EditorAudioMeterNotches); for (float db = 6.0f; db >= -80.0f; db -= 6.0f) { bool renderNotch = (db >= -6.0f || db == -24.0f || db == -72.0f); scale->add_notch(_scaled_db_to_normalized_volume(db), db, renderNotch); } + scale->set_mouse_filter(MOUSE_FILTER_PASS); hb->add_child(scale); effects = memnew(Tree); effects->set_hide_root(true); - effects->set_custom_minimum_size(Size2(0, 100) * EDSCALE); + effects->set_custom_minimum_size(Size2(0, 80) * EDSCALE); effects->set_hide_folding(true); effects->set_v_size_flags(SIZE_EXPAND_FILL); vb->add_child(effects); @@ -923,6 +947,36 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { delete_effect_popup->connect("index_pressed", this, "_delete_effect_pressed"); } +void EditorAudioBusDrop::_notification(int p_what) { + + switch (p_what) { + case NOTIFICATION_DRAW: { + draw_style_box(get_stylebox("normal", "Button"), Rect2(Vector2(), get_size())); + + if (hovering_drop) { + Color accent = get_color("accent_color", "Editor"); + accent.a *= 0.7; + draw_rect(Rect2(Point2(), get_size()), accent, false); + } + } break; + case NOTIFICATION_MOUSE_ENTER: { + + if (!hovering_drop) { + hovering_drop = true; + update(); + } + } break; + case NOTIFICATION_MOUSE_EXIT: + case NOTIFICATION_DRAG_END: { + + if (hovering_drop) { + hovering_drop = false; + update(); + } + } break; + } +} + bool EditorAudioBusDrop::can_drop_data(const Point2 &p_point, const Variant &p_data) const { Dictionary d = p_data; @@ -932,10 +986,11 @@ bool EditorAudioBusDrop::can_drop_data(const Point2 &p_point, const Variant &p_d return false; } + void EditorAudioBusDrop::drop_data(const Point2 &p_point, const Variant &p_data) { Dictionary d = p_data; - emit_signal("dropped", d["index"], -1); + emit_signal("dropped", d["index"], AudioServer::get_singleton()->get_bus_count()); } void EditorAudioBusDrop::_bind_methods() { @@ -944,6 +999,8 @@ void EditorAudioBusDrop::_bind_methods() { } EditorAudioBusDrop::EditorAudioBusDrop() { + + hovering_drop = false; } void EditorAudioBuses::_update_buses() { @@ -976,37 +1033,43 @@ EditorAudioBuses *EditorAudioBuses::register_editor() { void EditorAudioBuses::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - _update_buses(); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { - if (p_what == NOTIFICATION_DRAG_END) { - if (drop_end) { - drop_end->queue_delete(); - drop_end = NULL; - } - } + bus_scroll->add_style_override("bg", get_stylebox("bg", "Tree")); + } break; + case NOTIFICATION_READY: { - if (p_what == NOTIFICATION_PROCESS) { + _update_buses(); + } break; + case NOTIFICATION_DRAG_END: { - //check if anything was edited - bool edited = AudioServer::get_singleton()->is_edited(); - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - for (int j = 0; j < AudioServer::get_singleton()->get_bus_effect_count(i); j++) { - Ref<AudioEffect> effect = AudioServer::get_singleton()->get_bus_effect(i, j); - if (effect->is_edited()) { - edited = true; - effect->set_edited(false); + if (drop_end) { + drop_end->queue_delete(); + drop_end = NULL; + } + } break; + case NOTIFICATION_PROCESS: { + + // Check if anything was edited. + bool edited = AudioServer::get_singleton()->is_edited(); + for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { + for (int j = 0; j < AudioServer::get_singleton()->get_bus_effect_count(i); j++) { + Ref<AudioEffect> effect = AudioServer::get_singleton()->get_bus_effect(i, j); + if (effect->is_edited()) { + edited = true; + effect->set_edited(false); + } } } - } - - AudioServer::get_singleton()->set_edited(false); - if (edited) { + AudioServer::get_singleton()->set_edited(false); - save_timer->start(); - } + if (edited) { + save_timer->start(); + } + } break; } } @@ -1014,7 +1077,6 @@ void EditorAudioBuses::_add_bus() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - //need to simulate new name, so we can undi :( ur->create_action(TTR("Add Audio Bus")); ur->add_do_method(AudioServer::get_singleton(), "set_bus_count", AudioServer::get_singleton()->get_bus_count() + 1); ur->add_undo_method(AudioServer::get_singleton(), "set_bus_count", AudioServer::get_singleton()->get_bus_count()); @@ -1119,21 +1181,12 @@ void EditorAudioBuses::_request_drop_end() { void EditorAudioBuses::_drop_at_index(int p_bus, int p_index) { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - - //need to simulate new name, so we can undi :( ur->create_action(TTR("Move Audio Bus")); + ur->add_do_method(AudioServer::get_singleton(), "move_bus", p_bus, p_index); - int final_pos; - if (p_index == p_bus) { - final_pos = p_bus; - } else if (p_index == -1) { - final_pos = AudioServer::get_singleton()->get_bus_count() - 1; - } else if (p_index < p_bus) { - final_pos = p_index; - } else { - final_pos = p_index - 1; - } - ur->add_undo_method(AudioServer::get_singleton(), "move_bus", final_pos, p_bus); + int real_bus = p_index > p_bus ? p_bus : p_bus + 1; + int real_index = p_index > p_bus ? p_index - 1 : p_index; + ur->add_undo_method(AudioServer::get_singleton(), "move_bus", real_index, real_bus); ur->add_do_method(this, "_update_buses"); ur->add_undo_method(this, "_update_buses"); @@ -1189,7 +1242,7 @@ void EditorAudioBuses::_load_default_layout() { } edited_path = layout_path; - file->set_text(layout_path.get_file()); + file->set_text(String(TTR("Layout")) + ": " + layout_path.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _update_buses(); EditorNode::get_singleton()->get_undo_redo()->clear_history(); @@ -1206,7 +1259,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { } edited_path = p_string; - file->set_text(p_string.get_file()); + file->set_text(String(TTR("Layout")) + ": " + p_string.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _update_buses(); EditorNode::get_singleton()->get_undo_redo()->clear_history(); @@ -1228,7 +1281,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { } edited_path = p_string; - file->set_text(p_string.get_file()); + file->set_text(String(TTR("Layout")) + ": " + p_string.get_file()); _update_buses(); EditorNode::get_singleton()->get_undo_redo()->clear_history(); call_deferred("_select_layout"); @@ -1262,19 +1315,20 @@ EditorAudioBuses::EditorAudioBuses() { top_hb = memnew(HBoxContainer); add_child(top_hb); - file = memnew(ToolButton); - file->set_text("default_bus_layout.tres"); + file = memnew(Label); + file->set_text(String(TTR("Layout")) + ": " + "default_bus_layout.tres"); + file->set_clip_text(true); + file->set_h_size_flags(SIZE_EXPAND_FILL); top_hb->add_child(file); - file->connect("pressed", this, "_select_layout"); add = memnew(Button); top_hb->add_child(add); add->set_text(TTR("Add Bus")); add->set_tooltip(TTR("Add a new Audio Bus to this layout.")); - add->connect("pressed", this, "_add_bus"); - top_hb->add_spacer(); + VSeparator *separator = memnew(VSeparator); + top_hb->add_child(separator); load = memnew(Button); load->set_text(TTR("Load")); @@ -1301,7 +1355,6 @@ EditorAudioBuses::EditorAudioBuses() { _new->connect("pressed", this, "_new_layout"); bus_scroll = memnew(ScrollContainer); - bus_scroll->add_style_override("panel", memnew(StyleBoxEmpty)); bus_scroll->set_v_size_flags(SIZE_EXPAND_FILL); bus_scroll->set_enable_h_scroll(true); bus_scroll->set_enable_v_scroll(false); @@ -1377,38 +1430,65 @@ AudioBusesEditorPlugin::AudioBusesEditorPlugin(EditorAudioBuses *p_node) { AudioBusesEditorPlugin::~AudioBusesEditorPlugin() { } -void EditorAudioMeterNotches::add_notch(float normalized_offset, float db_value, bool render_value) { - notches.push_back(AudioNotch(normalized_offset, db_value, render_value)); +void EditorAudioMeterNotches::add_notch(float p_normalized_offset, float p_db_value, bool p_render_value) { + + notches.push_back(AudioNotch(p_normalized_offset, p_db_value, p_render_value)); +} + +Size2 EditorAudioMeterNotches::get_minimum_size() const { + + Ref<Font> font = get_font("font", "Label"); + float font_height = font->get_height(); + + float width = 0; + float height = top_padding + btm_padding; + + for (uint8_t i = 0; i < notches.size(); i++) { + if (notches[i].render_db_value) { + width = MAX(width, font->get_string_size(String::num(Math::abs(notches[i].db_value)) + "dB").x); + height += font_height; + } + } + width += line_length + label_space; + + return Size2(width, height); } void EditorAudioMeterNotches::_bind_methods() { + ClassDB::bind_method("add_notch", &EditorAudioMeterNotches::add_notch); ClassDB::bind_method("_draw_audio_notches", &EditorAudioMeterNotches::_draw_audio_notches); } void EditorAudioMeterNotches::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1.0f, 1.0f, 1.0f, 0.8f) : Color(0.0f, 0.0f, 0.0f, 0.8f); - _draw_audio_notches(); + + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(0, 0, 0); + } break; + case NOTIFICATION_DRAW: { + _draw_audio_notches(); + } break; } } void EditorAudioMeterNotches::_draw_audio_notches() { + Ref<Font> font = get_font("font", "Label"); float font_height = font->get_height(); for (uint8_t i = 0; i < notches.size(); i++) { AudioNotch n = notches[i]; - draw_line(Vector2(0.0f, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), + draw_line(Vector2(0, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), Vector2(line_length, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), notch_color, - 1.0f); + 1); if (n.render_db_value) { draw_string(font, Vector2(line_length + label_space, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + (font_height / 4) + top_padding), - String("{0}dB").format(varray(Math::abs(n.db_value))), + String::num(Math::abs(n.db_value)) + "dB", notch_color); } } @@ -1419,7 +1499,6 @@ EditorAudioMeterNotches::EditorAudioMeterNotches() : label_space(2.0f), btm_padding(9.0f), top_padding(5.0f) { - this->set_v_size_flags(SIZE_EXPAND_FILL); - this->set_h_size_flags(SIZE_EXPAND_FILL); - notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1.0f, 1.0f, 1.0f, 0.8f) : Color(0.0f, 0.0f, 0.0f, 0.8f); + + notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(0, 0, 0); } diff --git a/editor/editor_audio_buses.h b/editor/editor_audio_buses.h index 50f2101fd8..20890fd3b5 100644 --- a/editor/editor_audio_buses.h +++ b/editor/editor_audio_buses.h @@ -72,7 +72,6 @@ class EditorAudioBus : public PanelContainer { TextureProgress *vu_r; } channel[CHANNELS_MAX]; - class EditorAudioMeterNotches *scale; OptionButton *send; PopupMenu *effect_options; @@ -90,8 +89,8 @@ class EditorAudioBus : public PanelContainer { Tree *effects; bool updating_bus; - bool is_master; + mutable bool hovering_drop; void _gui_input(const Ref<InputEvent> &p_event); void _bus_popup_pressed(int p_option); @@ -137,15 +136,18 @@ public: EditorAudioBus(EditorAudioBuses *p_buses = NULL, bool p_is_master = false); }; -class EditorAudioBusDrop : public Panel { +class EditorAudioBusDrop : public Control { - GDCLASS(EditorAudioBusDrop, Panel); + GDCLASS(EditorAudioBusDrop, Control); virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; virtual void drop_data(const Point2 &p_point, const Variant &p_data); + mutable bool hovering_drop; + protected: static void _bind_methods(); + void _notification(int p_what); public: EditorAudioBusDrop(); @@ -157,13 +159,14 @@ class EditorAudioBuses : public VBoxContainer { HBoxContainer *top_hb; - Button *add; ScrollContainer *bus_scroll; HBoxContainer *bus_hb; EditorAudioBusDrop *drop_end; - Button *file; + Label *file; + + Button *add; Button *load; Button *save_as; Button *_default; @@ -242,7 +245,8 @@ public: float top_padding; Color notch_color; - void add_notch(float normalized_offset, float db_value, bool render_value = false); + void add_notch(float p_normalized_offset, float p_db_value, bool p_render_value = false); + Size2 get_minimum_size() const; private: static void _bind_methods(); diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 1a293adb4b..8025fc9795 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -63,6 +63,7 @@ void EditorFileDialog::_notification(int p_what) { dir_up->set_icon(get_icon("ArrowUp", "EditorIcons")); refresh->set_icon(get_icon("Reload", "EditorIcons")); favorite->set_icon(get_icon("Favorites", "EditorIcons")); + show_hidden->set_icon(get_icon("GuiVisibilityVisible", "EditorIcons")); fav_up->set_icon(get_icon("MoveUp", "EditorIcons")); fav_down->set_icon(get_icon("MoveDown", "EditorIcons")); @@ -86,9 +87,9 @@ void EditorFileDialog::_notification(int p_what) { } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - bool show_hidden = EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"); - if (show_hidden_files != show_hidden) - set_show_hidden_files(show_hidden); + bool is_showing_hidden = EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"); + if (show_hidden_files != is_showing_hidden) + set_show_hidden_files(is_showing_hidden); set_display_mode((DisplayMode)EditorSettings::get_singleton()->get("filesystem/file_dialog/display_mode").operator int()); // update icons @@ -140,7 +141,7 @@ void EditorFileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { handled = true; } if (ED_IS_SHORTCUT("file_dialog/toggle_favorite", p_event)) { - _favorite_toggled(favorite->is_pressed()); + _favorite_pressed(); handled = true; } if (ED_IS_SHORTCUT("file_dialog/toggle_mode", p_event)) { @@ -231,6 +232,7 @@ void EditorFileDialog::_file_entered(const String &p_file) { } void EditorFileDialog::_save_confirm_pressed() { + String f = dir_access->get_current_dir().plus_file(file->get_text()); _save_to_recent(); hide(); @@ -717,20 +719,19 @@ void EditorFileDialog::update_file_list() { List<String> files; List<String> dirs; - bool isdir; - bool ishidden; - bool show_hidden = show_hidden_files; + bool is_dir; + bool is_hidden; String item; - while ((item = dir_access->get_next(&isdir)) != "") { + while ((item = dir_access->get_next(&is_dir)) != "") { if (item == "." || item == "..") continue; - ishidden = dir_access->current_is_hidden(); + is_hidden = dir_access->current_is_hidden(); - if (show_hidden || !ishidden) { - if (!isdir) + if (show_hidden_files || !is_hidden) { + if (!is_dir) files.push_back(item); else dirs.push_back(item); @@ -1130,6 +1131,7 @@ void EditorFileDialog::_update_drives() { } void EditorFileDialog::_favorite_selected(int p_idx) { + dir_access->change_dir(favorites->get_item_metadata(p_idx)); file->set_text(""); update_dir(); @@ -1210,7 +1212,7 @@ void EditorFileDialog::_update_favorites() { favorites->add_item(name, folder_icon); } else { - continue; // We don't handle favorite files here + continue; // We don't handle favorite files here. } favorites->set_item_metadata(favorites->get_item_count() - 1, favorited[i]); @@ -1218,11 +1220,12 @@ void EditorFileDialog::_update_favorites() { if (setthis) { favorite->set_pressed(true); favorites->set_current(favorites->get_item_count() - 1); + recent->unselect_all(); } } } -void EditorFileDialog::_favorite_toggled(bool p_toggle) { +void EditorFileDialog::_favorite_pressed() { bool res = access == ACCESS_RESOURCES; String cd = get_current_dir(); @@ -1253,12 +1256,6 @@ void EditorFileDialog::_favorite_toggled(bool p_toggle) { _update_favorites(); } -void EditorFileDialog::_favorite_pressed() { - - favorite->set_pressed(!favorite->is_pressed()); - _favorite_toggled(favorite->is_pressed()); -} - void EditorFileDialog::_recent_selected(int p_idx) { Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs(); @@ -1381,7 +1378,6 @@ void EditorFileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_go_forward"), &EditorFileDialog::_go_forward); ClassDB::bind_method(D_METHOD("_go_up"), &EditorFileDialog::_go_up); - ClassDB::bind_method(D_METHOD("_favorite_toggled"), &EditorFileDialog::_favorite_toggled); ClassDB::bind_method(D_METHOD("_favorite_pressed"), &EditorFileDialog::_favorite_pressed); ClassDB::bind_method(D_METHOD("_favorite_selected"), &EditorFileDialog::_favorite_selected); ClassDB::bind_method(D_METHOD("_favorite_move_up"), &EditorFileDialog::_favorite_move_up); @@ -1418,6 +1414,7 @@ void EditorFileDialog::_bind_methods() { void EditorFileDialog::set_show_hidden_files(bool p_show) { show_hidden_files = p_show; + show_hidden->set_pressed(p_show); invalidate(); } @@ -1523,17 +1520,23 @@ EditorFileDialog::EditorFileDialog() { pathhb->add_child(refresh); favorite = memnew(ToolButton); - favorite->set_flat(true); favorite->set_toggle_mode(true); favorite->set_tooltip(TTR("(Un)favorite current folder.")); favorite->connect("pressed", this, "_favorite_pressed"); pathhb->add_child(favorite); - Ref<ButtonGroup> view_mode_group; - view_mode_group.instance(); + show_hidden = memnew(ToolButton); + show_hidden->set_toggle_mode(true); + show_hidden->set_pressed(is_showing_hidden_files()); + show_hidden->set_tooltip(TTR("Toggle visibility of hidden files.")); + show_hidden->connect("toggled", this, "set_show_hidden_files"); + pathhb->add_child(show_hidden); pathhb->add_child(memnew(VSeparator)); + Ref<ButtonGroup> view_mode_group; + view_mode_group.instance(); + mode_thumbnails = memnew(ToolButton); mode_thumbnails->connect("pressed", this, "set_display_mode", varray(DISPLAY_THUMBNAILS)); mode_thumbnails->set_toggle_mode(true); @@ -1593,6 +1596,7 @@ EditorFileDialog::EditorFileDialog() { rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE); rec_vb->set_v_size_flags(SIZE_EXPAND_FILL); recent = memnew(ItemList); + recent->set_allow_reselect(true); rec_vb->add_margin_child(TTR("Recent:"), recent, true); recent->connect("item_selected", this, "_recent_selected"); @@ -1609,7 +1613,7 @@ EditorFileDialog::EditorFileDialog() { list_vb->add_child(memnew(Label(TTR("Directories & Files:")))); preview_hb->add_child(list_vb); - // Item (files and folders) list with context menu + // Item (files and folders) list with context menu. item_list = memnew(ItemList); item_list->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1622,7 +1626,7 @@ EditorFileDialog::EditorFileDialog() { item_menu->connect("id_pressed", this, "_item_menu_id_pressed"); add_child(item_menu); - // Other stuff + // Other stuff. preview_vb = memnew(VBoxContainer); preview_hb->add_child(preview_vb); @@ -1641,7 +1645,7 @@ EditorFileDialog::EditorFileDialog() { filter = memnew(OptionButton); filter->set_stretch_ratio(3); filter->set_h_size_flags(SIZE_EXPAND_FILL); - filter->set_clip_text(true); // too many extensions overflow it + filter->set_clip_text(true); // Too many extensions overflow it. filename_hbc->add_child(filter); filename_hbc->set_h_size_flags(SIZE_EXPAND_FILL); item_vb->add_child(filename_hbc); diff --git a/editor/editor_file_dialog.h b/editor/editor_file_dialog.h index 529aaa71de..6578be8563 100644 --- a/editor/editor_file_dialog.h +++ b/editor/editor_file_dialog.h @@ -116,11 +116,13 @@ private: DirAccess *dir_access; ConfirmationDialog *confirm_save; DependencyRemoveDialog *remove_dialog; + ToolButton *mode_thumbnails; ToolButton *mode_list; ToolButton *refresh; ToolButton *favorite; + ToolButton *show_hidden; ToolButton *fav_up; ToolButton *fav_down; @@ -150,7 +152,6 @@ private: void update_filters(); void _update_favorites(); - void _favorite_toggled(bool p_toggle); void _favorite_pressed(); void _favorite_selected(int p_idx); void _favorite_move_up(); diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index cddabbc4e4..cf14cf3e00 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -206,7 +206,7 @@ void editor_register_fonts(Ref<Theme> p_theme) { dfmono->set_font_ptr(_font_Hack_Regular, _font_Hack_Regular_size); //dfd->set_force_autohinter(true); //just looks better..i think? - int default_font_size = int(EditorSettings::get_singleton()->get("interface/editor/main_font_size")) * EDSCALE; + int default_font_size = int(EDITOR_GET("interface/editor/main_font_size")) * EDSCALE; // Default font MAKE_DEFAULT_FONT(df, default_font_size); @@ -221,9 +221,9 @@ void editor_register_fonts(Ref<Theme> p_theme) { p_theme->set_font("title", "EditorFonts", df_title); // Doc font - MAKE_BOLD_FONT(df_doc_title, int(EDITOR_DEF("text_editor/help/help_title_font_size", 23)) * EDSCALE); + MAKE_BOLD_FONT(df_doc_title, int(EDITOR_GET("text_editor/help/help_title_font_size")) * EDSCALE); - MAKE_DEFAULT_FONT(df_doc, int(EDITOR_DEF("text_editor/help/help_font_size", 15)) * EDSCALE); + MAKE_DEFAULT_FONT(df_doc, int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE); p_theme->set_font("doc", "EditorFonts", df_doc); p_theme->set_font("doc_title", "EditorFonts", df_doc_title); @@ -236,13 +236,13 @@ void editor_register_fonts(Ref<Theme> p_theme) { p_theme->set_font("rulers", "EditorFonts", df_rulers); // Code font - MAKE_SOURCE_FONT(df_code, int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) * EDSCALE); + MAKE_SOURCE_FONT(df_code, int(EDITOR_GET("interface/editor/code_font_size")) * EDSCALE); p_theme->set_font("source", "EditorFonts", df_code); - MAKE_SOURCE_FONT(df_expression, (int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) - 1) * EDSCALE); + MAKE_SOURCE_FONT(df_expression, (int(EDITOR_GET("interface/editor/code_font_size")) - 1) * EDSCALE); p_theme->set_font("expression", "EditorFonts", df_expression); - MAKE_SOURCE_FONT(df_output_code, int(EDITOR_DEF("run/output/font_size", 13)) * EDSCALE); + MAKE_SOURCE_FONT(df_output_code, int(EDITOR_GET("run/output/font_size")) * EDSCALE); p_theme->set_font("output_source", "EditorFonts", df_output_code); MAKE_SOURCE_FONT(df_text_editor_status_code, default_font_size); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 5fc7e71cca..79c312b7b1 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5044,7 +5044,7 @@ void EditorNode::_feature_profile_changed() { main_editor_buttons[EDITOR_3D]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D)); main_editor_buttons[EDITOR_SCRIPT]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT)); main_editor_buttons[EDITOR_ASSETLIB]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB)); - if (profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D) || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB) || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB)) { + if (profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D) || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT) || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB)) { _editor_select(EDITOR_2D); } } else { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 4050899ccc..58e3cc6fc1 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -320,19 +320,19 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/custom_display_scale", 1.0f); hints["interface/editor/custom_display_scale"] = PropertyInfo(Variant::REAL, "interface/editor/custom_display_scale", PROPERTY_HINT_RANGE, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/main_font_size", 14); - hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "10,40,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); - _initial_set("interface/editor/code_font_size", 14); - hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); + hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "8,48,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/main_font_antialiased", true); - _initial_set("interface/editor/code_font_antialiased", true); _initial_set("interface/editor/main_font_hinting", 2); hints["interface/editor/main_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/main_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); - _initial_set("interface/editor/code_font_hinting", 2); - hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font", ""); hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font_bold", ""); hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); + _initial_set("interface/editor/code_font_size", 14); + hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,48,1", PROPERTY_USAGE_DEFAULT); + _initial_set("interface/editor/code_font_antialiased", true); + _initial_set("interface/editor/code_font_hinting", 2); + hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/code_font", ""); hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/dim_editor_on_dialog_popup", true); @@ -491,8 +491,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // Help _initial_set("text_editor/help/show_help_index", true); - _initial_set("text_editor/help_source_font_size", 14); - hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::REAL, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "10, 50, 1"); + _initial_set("text_editor/help/help_font_size", 15); + hints["text_editor/help/help_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_font_size", PROPERTY_HINT_RANGE, "8,48,1"); + _initial_set("text_editor/help/help_source_font_size", 14); + hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "8,48,1"); + _initial_set("text_editor/help/help_title_font_size", 23); + hints["text_editor/help/help_title_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_title_font_size", PROPERTY_HINT_RANGE, "8,48,1"); /* Editors */ @@ -569,7 +573,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/2d/bone_outline_size", 2); _initial_set("editors/2d/viewport_border_color", Color(0.4, 0.4, 1.0, 0.4)); _initial_set("editors/2d/warped_mouse_panning", true); - _initial_set("editors/2d/simple_spacebar_panning", false); + _initial_set("editors/2d/simple_panning", false); _initial_set("editors/2d/scroll_to_pan", false); _initial_set("editors/2d/pan_speed", 20); @@ -600,7 +604,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("run/auto_save/save_before_running", true); // Output - hints["run/output/font_size"] = PropertyInfo(Variant::INT, "run/output/font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); + _initial_set("run/output/font_size", 13); + hints["run/output/font_size"] = PropertyInfo(Variant::INT, "run/output/font_size", PROPERTY_HINT_RANGE, "8,48,1"); _initial_set("run/output/always_clear_output_on_play", true); _initial_set("run/output/always_open_output_on_play", true); _initial_set("run/output/always_close_output_on_stop", false); @@ -699,6 +704,10 @@ bool EditorSettings::_save_text_editor_theme(String p_file) { return false; } +bool EditorSettings::_is_default_text_editor_theme(String p_theme_name) { + return p_theme_name == "default" || p_theme_name == "adaptive" || p_theme_name == "custom"; +} + static Dictionary _get_builtin_script_templates() { Dictionary templates; @@ -1291,7 +1300,7 @@ void EditorSettings::list_text_editor_themes() { d->list_dir_begin(); String file = d->get_next(); while (file != String()) { - if (file.get_extension() == "tet" && file.get_basename().to_lower() != "default" && file.get_basename().to_lower() != "adaptive" && file.get_basename().to_lower() != "custom") { + if (file.get_extension() == "tet" && !_is_default_text_editor_theme(file.get_basename().to_lower())) { custom_themes.push_back(file.get_basename()); } file = d->get_next(); @@ -1308,14 +1317,16 @@ void EditorSettings::list_text_editor_themes() { } void EditorSettings::load_text_editor_theme() { - if (get("text_editor/theme/color_theme") == "Default" || get("text_editor/theme/color_theme") == "Adaptive" || get("text_editor/theme/color_theme") == "Custom") { - if (get("text_editor/theme/color_theme") == "Default") { + String p_file = get("text_editor/theme/color_theme"); + + if (_is_default_text_editor_theme(p_file.get_file().to_lower())) { + if (p_file == "Default") { _load_default_text_editor_theme(); } return; // sorry for "Settings changed" console spam } - String theme_path = get_text_editor_themes_dir().plus_file((String)get("text_editor/theme/color_theme") + ".tet"); + String theme_path = get_text_editor_themes_dir().plus_file(p_file + ".tet"); Ref<ConfigFile> cf = memnew(ConfigFile); Error err = cf->load(theme_path); @@ -1367,7 +1378,7 @@ bool EditorSettings::save_text_editor_theme() { String p_file = get("text_editor/theme/color_theme"); - if (p_file.get_file().to_lower() == "default" || p_file.get_file().to_lower() == "adaptive" || p_file.get_file().to_lower() == "custom") { + if (_is_default_text_editor_theme(p_file.get_file().to_lower())) { return false; } String theme_path = get_text_editor_themes_dir().plus_file(p_file + ".tet"); @@ -1379,7 +1390,7 @@ bool EditorSettings::save_text_editor_theme_as(String p_file) { p_file += ".tet"; } - if (p_file.get_file().to_lower() == "default.tet" || p_file.get_file().to_lower() == "adaptive.tet" || p_file.get_file().to_lower() == "custom.tet") { + if (_is_default_text_editor_theme(p_file.get_file().to_lower().trim_suffix(".tet"))) { return false; } if (_save_text_editor_theme(p_file)) { @@ -1397,6 +1408,11 @@ bool EditorSettings::save_text_editor_theme_as(String p_file) { return false; } +bool EditorSettings::is_default_text_editor_theme() { + String p_file = get("text_editor/theme/color_theme"); + return _is_default_text_editor_theme(p_file.get_file().to_lower()); +} + Vector<String> EditorSettings::get_script_templates(const String &p_extension) { Vector<String> templates; diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 43a8cbf739..2ee8dd805b 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -123,6 +123,7 @@ private: void _load_defaults(Ref<ConfigFile> p_extra_config = NULL); void _load_default_text_editor_theme(); bool _save_text_editor_theme(String p_file); + bool _is_default_text_editor_theme(String p_file); protected: static void _bind_methods(); @@ -187,6 +188,7 @@ public: bool import_text_editor_theme(String p_file); bool save_text_editor_theme(); bool save_text_editor_theme_as(String p_file); + bool is_default_text_editor_theme(); Vector<String> get_script_templates(const String &p_extension); String get_editor_layouts_config() const; diff --git a/editor/icons/icon_project_icon_loading.svg b/editor/icons/icon_project_icon_loading.svg new file mode 100644 index 0000000000..3802b67654 --- /dev/null +++ b/editor/icons/icon_project_icon_loading.svg @@ -0,0 +1 @@ +<svg height="64" viewBox="0 0 64 64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="m8 0c-4.432 0-8 3.568-8 8v48c0 4.432 3.568 8 8 8h48c4.432 0 8-3.568 8-8v-48c0-4.432-3.568-8-8-8z" fill="#e0e0e0" fill-opacity=".188235"/></svg>
\ No newline at end of file diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index fc5c8dbbb4..1503258ff5 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -35,7 +35,7 @@ #include "editor_node.h" #include "editor_settings.h" -void EditorAssetLibraryItem::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, int p_rating, const String &p_cost) { +void EditorAssetLibraryItem::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost) { title->set_text(p_title); asset_id = p_asset_id; @@ -44,13 +44,6 @@ void EditorAssetLibraryItem::configure(const String &p_title, int p_asset_id, co author->set_text(p_author); author_id = p_author_id; price->set_text(p_cost); - - for (int i = 0; i < 5; i++) { - if (i < p_rating) - stars[i]->set_texture(get_icon("Favorites", "EditorIcons")); - else - stars[i]->set_texture(get_icon("NonFavorite", "EditorIcons")); - } } void EditorAssetLibraryItem::set_image(int p_type, int p_index, const Ref<Texture> &p_image) { @@ -65,9 +58,10 @@ void EditorAssetLibraryItem::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - icon->set_normal_texture(get_icon("DefaultProjectIcon", "EditorIcons")); + icon->set_normal_texture(get_icon("ProjectIconLoading", "EditorIcons")); category->add_color_override("font_color", Color(0.5, 0.5, 0.5)); author->add_color_override("font_color", Color(0.5, 0.5, 0.5)); + price->add_color_override("font_color", Color(0.5, 0.5, 0.5)); } } @@ -141,13 +135,6 @@ EditorAssetLibraryItem::EditorAssetLibraryItem() { author->connect("pressed", this, "_author_clicked"); vb->add_child(author); - HBoxContainer *rating_hb = memnew(HBoxContainer); - vb->add_child(rating_hb); - - for (int i = 0; i < 5; i++) { - stars[i] = memnew(TextureRect); - rating_hb->add_child(stars[i]); - } price = memnew(Label); price->set_text(TTR("Free")); vb->add_child(price); @@ -248,13 +235,13 @@ void EditorAssetLibraryItemDescription::_preview_click(int p_id) { } } -void EditorAssetLibraryItemDescription::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, int p_rating, const String &p_cost, int p_version, const String &p_version_string, const String &p_description, const String &p_download_url, const String &p_browse_url, const String &p_sha256_hash) { +void EditorAssetLibraryItemDescription::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost, int p_version, const String &p_version_string, const String &p_description, const String &p_download_url, const String &p_browse_url, const String &p_sha256_hash) { asset_id = p_asset_id; title = p_title; download_url = p_download_url; sha256 = p_sha256_hash; - item->configure(p_title, p_asset_id, p_category, p_category_id, p_author, p_author_id, p_rating, p_cost); + item->configure(p_title, p_asset_id, p_category, p_category_id, p_author, p_author_id, p_cost); description->clear(); description->add_text(TTR("Version:") + " " + p_version_string + "\n"); description->add_text(TTR("Contents:") + " "); @@ -666,7 +653,6 @@ void EditorAssetLibrary::_install_asset() { } const char *EditorAssetLibrary::sort_key[SORT_MAX] = { - "rating", "downloads", "name", "cost", @@ -674,10 +660,9 @@ const char *EditorAssetLibrary::sort_key[SORT_MAX] = { }; const char *EditorAssetLibrary::sort_text[SORT_MAX] = { - "Rating", "Downloads", "Name", - "Cost", + "License", // "cost" stores the SPDX license name in the Godot Asset Library "Updated" }; @@ -1212,12 +1197,11 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const ERR_CONTINUE(!r.has("author_id")); ERR_CONTINUE(!r.has("category_id")); ERR_FAIL_COND(!category_map.has(r["category_id"])); - ERR_CONTINUE(!r.has("rating")); ERR_CONTINUE(!r.has("cost")); EditorAssetLibraryItem *item = memnew(EditorAssetLibraryItem); asset_items->add_child(item); - item->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["rating"], r["cost"]); + item->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["cost"]); item->connect("asset_selected", this, "_select_asset"); item->connect("author_selected", this, "_select_author"); item->connect("category_selected", this, "_select_category"); @@ -1238,7 +1222,6 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const ERR_FAIL_COND(!r.has("version_string")); ERR_FAIL_COND(!r.has("category_id")); ERR_FAIL_COND(!category_map.has(r["category_id"])); - ERR_FAIL_COND(!r.has("rating")); ERR_FAIL_COND(!r.has("cost")); ERR_FAIL_COND(!r.has("description")); ERR_FAIL_COND(!r.has("download_url")); @@ -1254,7 +1237,7 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const description->popup_centered_minsize(); description->connect("confirmed", this, "_install_asset"); - description->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["rating"], r["cost"], r["version"], r["version_string"], r["description"], r["download_url"], r["browse_url"], r["download_hash"]); + description->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["cost"], r["version"], r["version_string"], r["description"], r["download_url"], r["browse_url"], r["download_hash"]); /*item->connect("asset_selected",this,"_select_asset"); item->connect("author_selected",this,"_select_author"); item->connect("category_selected",this,"_category_selected");*/ diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index dd5f3c2077..81288ae831 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -77,7 +77,7 @@ protected: static void _bind_methods(); public: - void configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, int p_rating, const String &p_cost); + void configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost); EditorAssetLibraryItem(); }; @@ -120,7 +120,7 @@ protected: static void _bind_methods(); public: - void configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, int p_rating, const String &p_cost, int p_version, const String &p_version_string, const String &p_description, const String &p_download_url, const String &p_browse_url, const String &p_sha256_hash); + void configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost, int p_version, const String &p_version_string, const String &p_description, const String &p_download_url, const String &p_browse_url, const String &p_sha256_hash); void add_preview(int p_id, bool p_video, const String &p_url); String get_title() { return title; } @@ -216,7 +216,6 @@ class EditorAssetLibrary : public PanelContainer { }; enum SortOrder { - SORT_RATING, SORT_DOWNLOADS, SORT_NAME, SORT_COST, diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index e8ef689ca3..fbf01a9405 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1081,7 +1081,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event) { if (b->is_pressed() && (b->get_button_index() == BUTTON_MIDDLE || (b->get_button_index() == BUTTON_LEFT && tool == TOOL_PAN) || - (b->get_button_index() == BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_spacebar_panning") && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) { + (b->get_button_index() == BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { // Pan the viewport panning = true; } @@ -1097,7 +1097,9 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; if (k.is_valid()) { - if (k->get_scancode() == KEY_SPACE && (EditorSettings::get_singleton()->get("editors/2d/simple_spacebar_panning") || drag_type != DRAG_NONE)) { + bool is_pan_key = pan_view_shortcut.is_valid() && pan_view_shortcut->is_shortcut(p_event); + + if (is_pan_key && (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || drag_type != DRAG_NONE)) { if (!panning) { if (k->is_pressed() && !k->is_echo()) { //Pan the viewport @@ -1110,6 +1112,9 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event) { } } } + + if (is_pan_key) + pan_pressed = k->is_pressed(); } Ref<InputEventMouseMotion> m = p_event; @@ -2214,7 +2219,7 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { bool accepted = false; - if (EditorSettings::get_singleton()->get("editors/2d/simple_spacebar_panning") || !Input::get_singleton()->is_key_pressed(KEY_SPACE)) { + if (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || !pan_pressed) { if ((accepted = _gui_input_rulers_and_guides(p_event))) { //printf("Rulers and guides\n"); } else if ((accepted = editor->get_editor_plugins_over()->forward_gui_input(p_event))) { @@ -4780,6 +4785,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { dragged_guide_pos = Point2(); dragged_guide_index = -1; panning = false; + pan_pressed = false; bone_last_frame = 0; @@ -5129,6 +5135,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), KEY_KP_MULTIPLY); divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), KEY_KP_DIVIDE); + pan_view_shortcut = ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), KEY_SPACE); skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); singleton = this; diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index e098d261c0..ff221eb758 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -265,6 +265,7 @@ private: bool key_rot; bool key_scale; bool panning; + bool pan_pressed; MenuOption last_option; @@ -383,6 +384,7 @@ private: Ref<ShortCut> set_pivot_shortcut; Ref<ShortCut> multiply_grid_step_shortcut; Ref<ShortCut> divide_grid_step_shortcut; + Ref<ShortCut> pan_view_shortcut; bool _is_node_locked(const Node *p_node); bool _is_node_movable(const Node *p_node, bool p_popup_warning = false); diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 28e57ac48a..285823d95a 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -643,7 +643,7 @@ Ref<Texture> EditorAudioStreamPreviewPlugin::generate(const RES &p_from, const S float max = -1000; float min = 1000; int from = uint64_t(i) * frame_length / w; - int to = uint64_t(i + 1) * frame_length / w; + int to = (uint64_t(i) + 1) * frame_length / w; to = MIN(to, frame_length); from = MIN(from, frame_length - 1); if (to == from) { diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index f45e6a2c9a..7456c5d016 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1311,23 +1311,29 @@ void ScriptEditor::_theme_option(int p_option) { EditorSettings::get_singleton()->load_text_editor_theme(); } break; case THEME_SAVE: { - if (!EditorSettings::get_singleton()->save_text_editor_theme()) { + if (EditorSettings::get_singleton()->is_default_text_editor_theme()) { + ScriptEditor::_show_save_theme_as_dialog(); + } else if (!EditorSettings::get_singleton()->save_text_editor_theme()) { editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); } } break; case THEME_SAVE_AS: { - file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); - file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - file_dialog_option = THEME_SAVE_AS; - file_dialog->clear_filters(); - file_dialog->add_filter("*.tet"); - file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); - file_dialog->popup_centered_ratio(); - file_dialog->set_title(TTR("Save Theme As...")); + ScriptEditor::_show_save_theme_as_dialog(); } break; } } +void ScriptEditor::_show_save_theme_as_dialog() { + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_dialog_option = THEME_SAVE_AS; + file_dialog->clear_filters(); + file_dialog->add_filter("*.tet"); + file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); + file_dialog->popup_centered_ratio(); + file_dialog->set_title(TTR("Save Theme As...")); +} + void ScriptEditor::_tab_changed(int p_which) { ensure_select_current(); @@ -1976,10 +1982,11 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra String flags = EditorSettings::get_singleton()->get("text_editor/external/exec_flags"); List<String> args; + bool has_file_flag = false; + String script_path = ProjectSettings::get_singleton()->globalize_path(p_resource->get_path()); if (flags.size()) { String project_path = ProjectSettings::get_singleton()->get_resource_path(); - String script_path = ProjectSettings::get_singleton()->globalize_path(p_resource->get_path()); flags = flags.replacen("{line}", itos(p_line > 0 ? p_line : 0)); flags = flags.replacen("{col}", itos(p_col)); @@ -2001,6 +2008,9 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra } else if (flags[i] == '\0' || (!inside_quotes && flags[i] == ' ')) { String arg = flags.substr(from, num_chars); + if (arg.find("{file}") != -1) { + has_file_flag = true; + } // do path replacement here, else there will be issues with spaces and quotes arg = arg.replacen("{project}", project_path); @@ -2015,6 +2025,11 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra } } + // Default to passing script path if no {file} flag is specified. + if (!has_file_flag) { + args.push_back(script_path); + } + Error err = OS::get_singleton()->execute(path, args, false); if (err == OK) return false; @@ -3437,7 +3452,8 @@ ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "text_editor/open_scripts/list_script_names_as", PROPERTY_HINT_ENUM, "Name,Parent Directory And Name,Full Path")); EDITOR_DEF("text_editor/open_scripts/list_script_names_as", 0); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_path", PROPERTY_HINT_GLOBAL_FILE)); - EDITOR_DEF("text_editor/external/exec_flags", ""); + EDITOR_DEF("text_editor/external/exec_flags", "{file}"); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_flags", PROPERTY_HINT_PLACEHOLDER_TEXT, "Call flags with placeholders: {project}, {file}, {col}, {line}.")); ED_SHORTCUT("script_editor/open_recent", TTR("Open Recent"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_T); ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files")); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index fae98d6ec8..7cd347e2d0 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -264,6 +264,7 @@ class ScriptEditor : public PanelContainer { void _tab_changed(int p_which); void _menu_option(int p_option); void _theme_option(int p_option); + void _show_save_theme_as_dialog(); Tree *disk_changed_list; ConfirmationDialog *disk_changed; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 1b71f6800a..c7a438948d 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -547,7 +547,6 @@ void ScriptTextEditor::_validate_script() { script->set_source_code(text); script->update_exports(); _update_member_keywords(); - //script->reload(); //will update all the variables in property editors } functions.clear(); @@ -558,30 +557,36 @@ void ScriptTextEditor::_validate_script() { } _update_connected_methods(); - code_editor->set_warning_nb(missing_connections.size() + warnings.size()); + int warning_nb = warnings.size(); warnings_panel->clear(); - // add missing connections - Node *base = get_tree()->get_edited_scene_root(); - if (base && missing_connections.size() > 0) { - warnings_panel->push_table(1); - for (List<Connection>::Element *E = missing_connections.front(); E; E = E->next()) { - Connection connection = E->get(); - - String base_path = base->get_name(); - String source_path = base == connection.source ? base_path : base_path + "/" + String(base->get_path_to(Object::cast_to<Node>(connection.source))); - String target_path = base == connection.target ? base_path : base_path + "/" + String(base->get_path_to(Object::cast_to<Node>(connection.target))); + // Add missing connections. + if (GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { + Node *base = get_tree()->get_edited_scene_root(); + if (base && missing_connections.size() > 0) { + warnings_panel->push_table(1); + for (List<Connection>::Element *E = missing_connections.front(); E; E = E->next()) { + Connection connection = E->get(); + + String base_path = base->get_name(); + String source_path = base == connection.source ? base_path : base_path + "/" + String(base->get_path_to(Object::cast_to<Node>(connection.source))); + String target_path = base == connection.target ? base_path : base_path + "/" + String(base->get_path_to(Object::cast_to<Node>(connection.target))); + + warnings_panel->push_cell(); + warnings_panel->push_color(warnings_panel->get_color("warning_color", "Editor")); + warnings_panel->add_text(vformat(TTR("Missing connected method '%s' for signal '%s' from node '%s' to node '%s'."), connection.method, connection.signal, source_path, target_path)); + warnings_panel->pop(); // Color. + warnings_panel->pop(); // Cell. + } + warnings_panel->pop(); // Table. - warnings_panel->push_cell(); - warnings_panel->push_color(warnings_panel->get_color("warning_color", "Editor")); - warnings_panel->add_text(vformat(TTR("Missing connected method '%s' for signal '%s' from node '%s' to node '%s'"), connection.method, connection.signal, source_path, target_path)); - warnings_panel->pop(); // Color - warnings_panel->pop(); // Cell + warning_nb += missing_connections.size(); } - warnings_panel->pop(); // Table } - // add script warnings + code_editor->set_warning_nb(warning_nb); + + // Add script warnings. warnings_panel->push_table(3); for (List<ScriptLanguage::Warning>::Element *E = warnings.front(); E; E = E->next()) { ScriptLanguage::Warning w = E->get(); @@ -591,13 +596,13 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_color(warnings_panel->get_color("warning_color", "Editor")); warnings_panel->add_text(TTR("Line") + " " + itos(w.line)); warnings_panel->add_text(" (" + w.string_code + "):"); - warnings_panel->pop(); // Color - warnings_panel->pop(); // Meta goto - warnings_panel->pop(); // Cell + warnings_panel->pop(); // Color. + warnings_panel->pop(); // Meta goto. + warnings_panel->pop(); // Cell. warnings_panel->push_cell(); warnings_panel->add_text(w.message); - warnings_panel->pop(); // Cell + warnings_panel->pop(); // Cell. Dictionary ignore_meta; ignore_meta["line"] = w.line; @@ -605,11 +610,10 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_cell(); warnings_panel->push_meta(ignore_meta); warnings_panel->add_text(TTR("(ignore)")); - warnings_panel->pop(); // Meta ignore - warnings_panel->pop(); // Cell - //warnings_panel->add_newline(); + warnings_panel->pop(); // Meta ignore. + warnings_panel->pop(); // Cell. } - warnings_panel->pop(); // Table + warnings_panel->pop(); // Table. line--; bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true); @@ -878,12 +882,30 @@ void ScriptTextEditor::_update_connected_methods() { continue; } - int line = script->get_language()->find_function(connection.method, text_edit->get_text()); - if (line < 0) { - missing_connections.push_back(connection); - continue; + if (!ClassDB::has_method(script->get_instance_base_type(), connection.method)) { + + int line = script->get_language()->find_function(connection.method, text_edit->get_text()); + if (line < 0) { + // There is a chance that the method is inherited from another script. + bool found_inherited_function = false; + Ref<Script> inherited_script = script->get_base_script(); + while (!inherited_script.is_null()) { + line = inherited_script->get_language()->find_function(connection.method, inherited_script->get_source_code()); + if (line != -1) { + found_inherited_function = true; + break; + } + + inherited_script = inherited_script->get_base_script(); + } + + if (!found_inherited_function) { + missing_connections.push_back(connection); + } + } else { + text_edit->set_line_info_icon(line - 1, get_parent_control()->get_icon("Slot", "EditorIcons"), connection.method); + } } - text_edit->set_line_info_icon(line - 1, get_parent_control()->get_icon("Slot", "EditorIcons"), connection.method); } } } diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index a795405dfc..f9ca38b919 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -60,6 +60,26 @@ void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { _line_col_changed(); } +void ShaderTextEditor::reload_text() { + ERR_FAIL_COND(shader.is_null()); + + TextEdit *te = get_text_edit(); + int column = te->cursor_get_column(); + int row = te->cursor_get_line(); + int h = te->get_h_scroll(); + int v = te->get_v_scroll(); + + te->set_text(shader->get_code()); + te->cursor_set_line(row); + te->cursor_set_column(column); + te->set_h_scroll(h); + te->set_v_scroll(v); + + te->tag_saved_version(); + + update_line_and_column(); +} + void ShaderTextEditor::_load_theme_settings() { get_text_edit()->clear_colors(); @@ -330,9 +350,8 @@ void ShaderEditor::_menu_option(int p_option) { void ShaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - //if (is_visible_in_tree()) - // shader_editor->get_text_edit()->grab_focus(); + if (p_what == MainLoop::NOTIFICATION_WM_FOCUS_IN) { + _check_for_external_edit(); } } @@ -363,12 +382,14 @@ void ShaderEditor::_editor_settings_changed() { void ShaderEditor::_bind_methods() { + ClassDB::bind_method("_reload_shader_from_disk", &ShaderEditor::_reload_shader_from_disk); ClassDB::bind_method("_editor_settings_changed", &ShaderEditor::_editor_settings_changed); ClassDB::bind_method("_text_edit_gui_input", &ShaderEditor::_text_edit_gui_input); ClassDB::bind_method("_menu_option", &ShaderEditor::_menu_option); ClassDB::bind_method("_params_changed", &ShaderEditor::_params_changed); ClassDB::bind_method("apply_shaders", &ShaderEditor::apply_shaders); + ClassDB::bind_method("save_external_data", &ShaderEditor::save_external_data); } void ShaderEditor::ensure_select_current() { @@ -389,6 +410,37 @@ 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::_check_for_external_edit() { + + if (shader.is_null() || !shader.is_valid()) { + return; + } + + // internal shader. + if (shader->get_path() == "" || shader->get_path().find("local://") != -1 || shader->get_path().find("::") != -1) { + return; + } + + bool use_autoreload = bool(EDITOR_DEF("text_editor/files/auto_reload_scripts_on_external_change", false)); + 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("popup_centered"); + } + } +} + +void ShaderEditor::_reload_shader_from_disk() { + + Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), true); + ERR_FAIL_COND(!rel_shader.is_valid()); + + shader->set_code(rel_shader->get_code()); + shader->set_last_modified_time(rel_shader->get_last_modified_time()); + shader_editor->reload_text(); +} + void ShaderEditor::edit(const Ref<Shader> &p_shader) { if (p_shader.is_null() || !p_shader->is_text_shader()) @@ -405,16 +457,20 @@ void ShaderEditor::edit(const Ref<Shader> &p_shader) { // see if already has it } -void ShaderEditor::save_external_data() { +void ShaderEditor::save_external_data(const String &p_str) { - if (shader.is_null()) + if (shader.is_null()) { + disk_changed->hide(); return; - apply_shaders(); + } + apply_shaders(); if (shader->get_path() != "" && shader->get_path().find("local://") == -1 && shader->get_path().find("::") == -1) { //external shader, save it ResourceSaver::save(shader->get_path(), shader); } + + disk_changed->hide(); } void ShaderEditor::apply_shaders() { @@ -573,6 +629,23 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { 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 on disk.\nWhat action should be taken?")); + vbc->add_child(dl); + + disk_changed->connect("confirmed", this, "_reload_shader_from_disk"); + disk_changed->get_ok()->set_text(TTR("Reload")); + + disk_changed->add_button(TTR("Resave"), !OS::get_singleton()->get_swap_ok_cancel(), "resave"); + disk_changed->connect("custom_action", this, "save_external_data"); + + add_child(disk_changed); + _editor_settings_changed(); } diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 28ac9faaa5..b56c1451ad 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -58,6 +58,8 @@ protected: public: virtual void _validate_script(); + void reload_text(); + Ref<Shader> get_edited_shader() const; void set_edited_shader(const Ref<Shader> &p_shader); ShaderTextEditor(); @@ -103,6 +105,7 @@ class ShaderEditor : public PanelContainer { GotoLineDialog *goto_line_dialog; ConfirmationDialog *erase_tab_confirm; + ConfirmationDialog *disk_changed; ShaderTextEditor *shader_editor; @@ -112,6 +115,9 @@ class ShaderEditor : public PanelContainer { void _editor_settings_changed(); + void _check_for_external_edit(); + void _reload_shader_from_disk(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -127,7 +133,7 @@ public: void goto_line_selection(int p_line, int p_begin, int p_end); virtual Size2 get_minimum_size() const { return Size2(0, 200); } - void save_external_data(); + void save_external_data(const String &p_str = ""); ShaderEditor(EditorNode *p_node); }; diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index 5e1d818ce9..16f93b8fd3 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -572,23 +572,25 @@ void TileMapEditor::_pick_tile(const Point2 &p_pos) { if (id == TileMap::INVALID_CELL) return; - if (search_box->get_text().strip_edges() != "") { - + if (search_box->get_text() != "") { search_box->set_text(""); _update_palette(); } - Vector<int> selected; - - selected.push_back(id); - set_selected_tiles(selected); - flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y); flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y); transpose = node->is_cell_transposed(p_pos.x, p_pos.y); autotile_coord = node->get_cell_autotile_coord(p_pos.x, p_pos.y); + Vector<int> selected; + selected.push_back(id); + set_selected_tiles(selected); _update_palette(); + + if ((manual_autotile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::AUTO_TILE) || (!priority_atlastile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::ATLAS_TILE)) { + manual_palette->select(manual_palette->find_metadata((Point2)autotile_coord)); + } + CanvasItemEditor::get_singleton()->update_viewport(); } @@ -982,7 +984,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { return true; } else { - // Mousebutton was released + // Mousebutton was released. if (tool != TOOL_NONE) { if (tool == TOOL_PAINTING) { @@ -1046,7 +1048,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { CanvasItemEditor::get_singleton()->update_viewport(); - return true; // We want to keep the Pasting tool + return true; // We want to keep the Pasting tool. } else if (tool == TOOL_SELECTING) { CanvasItemEditor::get_singleton()->update_viewport(); @@ -1070,7 +1072,10 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { _finish_undo(); - // We want to keep the bucket-tool active + // So the fill preview is cleared right after the click. + CanvasItemEditor::get_singleton()->update_viewport(); + + // We want to keep the bucket-tool active. return true; } @@ -1197,7 +1202,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (tool == TOOL_PAINTING) { - // Paint using bresenham line to prevent holes in painting if the user moves fast + // Paint using bresenham line to prevent holes in painting if the user moves fast. Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); Vector<int> ids = get_selected_tiles(); @@ -1218,7 +1223,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (tool == TOOL_ERASING) { - // erase using bresenham line to prevent holes in painting if the user moves fast + // Erase using bresenham line to prevent holes in painting if the user moves fast. Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); @@ -1343,13 +1348,13 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } if (!mouse_over) { - // Editor shortcuts should not fire if mouse not in viewport + // Editor shortcuts should not fire if mouse not in viewport. return false; } if (ED_IS_SHORTCUT("tile_map_editor/paint_tile", p_event)) { // NOTE: We do not set tool = TOOL_PAINTING as this begins painting - // immediately without pressing the left mouse button first + // immediately without pressing the left mouse button first. tool = TOOL_NONE; CanvasItemEditor::get_singleton()->update_viewport(); @@ -1431,7 +1436,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { CanvasItemEditor::get_singleton()->update_viewport(); return true; } - } else if (k.is_valid()) { // release event + } else if (k.is_valid()) { // Release event. if (tool == TOOL_NONE) { @@ -1447,7 +1452,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { #else if (k->get_scancode() == KEY_CONTROL) { #endif - // go back to that last tool if KEY_CONTROL was released + // Go back to that last tool if KEY_CONTROL was released. tool = last_tool; CanvasItemEditor::get_singleton()->update_viewport(); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 7b521aba13..3fd497ed7b 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -961,9 +961,25 @@ void ProjectManager::_notification(int p_what) { set_process_unhandled_input(is_visible_in_tree()); } break; + case NOTIFICATION_WM_QUIT_REQUEST: { + + _dim_window(); + } break; } } +void ProjectManager::_dim_window() { + + // This method must be called before calling `get_tree()->quit()`. + // Otherwise, its effect won't be visible + + // Dim the project manager window while it's quitting to make it clearer that it's busy. + // No transition is applied, as the effect needs to be visible immediately + float c = 1.0f - float(EDITOR_GET("interface/editor/dim_amount")); + Color dim_color = Color(c, c, c); + gui_base->set_modulate(dim_color); +} + void ProjectManager::_panel_draw(Node *p_hb) { HBoxContainer *hb = Object::cast_to<HBoxContainer>(p_hb); @@ -1514,6 +1530,7 @@ void ProjectManager::_open_selected_projects() { ERR_FAIL_COND(err); } + _dim_window(); get_tree()->quit(); } @@ -1780,11 +1797,13 @@ void ProjectManager::_restart_confirm() { Error err = OS::get_singleton()->execute(exec, args, false, &pid); ERR_FAIL_COND(err); + _dim_window(); get_tree()->quit(); } void ProjectManager::_exit_dialog() { + _dim_window(); get_tree()->quit(); } diff --git a/editor/project_manager.h b/editor/project_manager.h index fa878e75a6..a7cc6549f5 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -110,6 +110,7 @@ class ProjectManager : public Control { void _install_project(const String &p_zip_path, const String &p_title); + void _dim_window(); void _panel_draw(Node *p_hb); void _panel_input(const Ref<InputEvent> &p_ev, Node *p_hb); void _unhandled_input(const Ref<InputEvent> &p_ev); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index e1b4320378..d6c8e6b452 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -291,8 +291,8 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { p_node->connect("script_changed", this, "_node_script_changed", varray(p_node)); if (!p_node->get_script().is_null()) { - - item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT, false, TTR("Open Script")); + Ref<Script> script = p_node->get_script(); + item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT, false, TTR("Open Script:") + " " + script->get_path()); } if (p_node->is_class("CanvasItem")) { @@ -399,15 +399,17 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { void SceneTreeEditor::_node_visibility_changed(Node *p_node) { - if (p_node != get_scene_node() && !p_node->get_owner()) { + if (!p_node || (p_node != get_scene_node() && !p_node->get_owner())) { return; } - TreeItem *item = p_node ? _find(tree->get_root(), p_node->get_path()) : NULL; - if (!item) { + TreeItem *item = _find(tree->get_root(), p_node->get_path()); + + if (!item) { return; } + int idx = item->get_button_by_id(0, BUTTON_VISIBILITY); ERR_FAIL_COND(idx == -1); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 751ae4fcf7..ffa221edaf 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -100,17 +100,25 @@ void ScriptCreateDialog::set_inheritance_base_type(const String &p_base) { base_type = p_base; } -bool ScriptCreateDialog::_validate(const String &p_string) { +bool ScriptCreateDialog::_validate_parent(const String &p_string) { if (p_string.length() == 0) return false; - if (ScriptServer::get_language(language_menu->get_selected())->can_inherit_from_file() && p_string.is_quoted()) { + if (can_inherit_from_file && p_string.is_quoted()) { String p = p_string.substr(1, p_string.length() - 2); if (_validate_path(p, true) == "") return true; } + return ClassDB::class_exists(p_string) || ScriptServer::is_global_class(p_string); +} + +bool ScriptCreateDialog::_validate_class(const String &p_string) { + + if (p_string.length() == 0) + return false; + for (int i = 0; i < p_string.length(); i++) { if (i == 0) { @@ -118,7 +126,7 @@ bool ScriptCreateDialog::_validate(const String &p_string) { return false; // no start with number plz } - bool valid_char = (p_string[i] >= '0' && p_string[i] <= '9') || (p_string[i] >= 'a' && p_string[i] <= 'z') || (p_string[i] >= 'A' && p_string[i] <= 'Z') || p_string[i] == '_' || p_string[i] == '-'; + bool valid_char = (p_string[i] >= '0' && p_string[i] <= '9') || (p_string[i] >= 'a' && p_string[i] <= 'z') || (p_string[i] >= 'A' && p_string[i] <= 'Z') || p_string[i] == '_'; if (!valid_char) return false; @@ -193,7 +201,7 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must void ScriptCreateDialog::_class_name_changed(const String &p_name) { - if (_validate(class_name->get_text())) { + if (_validate_class(class_name->get_text())) { is_class_name_valid = true; } else { is_class_name_valid = false; @@ -203,7 +211,7 @@ void ScriptCreateDialog::_class_name_changed(const String &p_name) { void ScriptCreateDialog::_parent_name_changed(const String &p_parent) { - if (_validate(parent_name->get_text())) { + if (_validate_parent(parent_name->get_text())) { is_parent_name_valid = true; } else { is_parent_name_valid = false; @@ -298,27 +306,13 @@ void ScriptCreateDialog::_load_exist() { void ScriptCreateDialog::_lang_changed(int l) { - l = language_menu->get_selected(); ScriptLanguage *language = ScriptServer::get_language(l); - if (language->has_named_classes()) { - has_named_classes = true; - } else { - has_named_classes = false; - } - - if (language->supports_builtin_mode()) { - supports_built_in = true; - } else { - supports_built_in = false; + has_named_classes = language->has_named_classes(); + can_inherit_from_file = language->can_inherit_from_file(); + supports_built_in = language->supports_builtin_mode(); + if (!supports_built_in) is_built_in = false; - } - - if (ScriptServer::get_language(l)->can_inherit_from_file()) { - can_inherit_from_file = true; - } else { - can_inherit_from_file = false; - } String selected_ext = "." + language->get_extension(); String path = file_path->get_text(); @@ -430,7 +424,7 @@ void ScriptCreateDialog::_file_selected(const String &p_file) { String p = ProjectSettings::get_singleton()->localize_path(p_file); if (is_browsing_parent) { parent_name->set_text("\"" + p + "\""); - _class_name_changed("\"" + p + "\""); + _parent_name_changed(parent_name->get_text()); } else { file_path->set_text(p); _path_changed(p); @@ -445,7 +439,8 @@ void ScriptCreateDialog::_file_selected(const String &p_file) { void ScriptCreateDialog::_create() { - parent_name->set_text(select_class->get_selected_type()); + parent_name->set_text(select_class->get_selected_type().split(" ")[0]); + _parent_name_changed(parent_name->get_text()); } void ScriptCreateDialog::_browse_class_in_tree() { @@ -542,14 +537,7 @@ void ScriptCreateDialog::_update_dialog() { class_name->set_editable(false); class_name->set_placeholder(TTR("N/A")); class_name->set_placeholder_alpha(1); - } - - /* Can script inherit from a file */ - - if (can_inherit_from_file) { - parent_browse_button->set_disabled(false); - } else { - parent_browse_button->set_disabled(true); + class_name->set_text(""); } /* Is script Built-in */ @@ -572,7 +560,8 @@ void ScriptCreateDialog::_update_dialog() { if (is_built_in) { get_ok()->set_text(TTR("Create")); parent_name->set_editable(true); - parent_browse_button->set_disabled(false); + parent_search_button->set_disabled(false); + parent_browse_button->set_disabled(!can_inherit_from_file); internal->set_visible(_can_be_built_in()); internal_label->set_visible(_can_be_built_in()); _msg_path_valid(true, TTR("Built-in script (into scene file).")); @@ -580,7 +569,8 @@ void ScriptCreateDialog::_update_dialog() { // New Script Created get_ok()->set_text(TTR("Create")); parent_name->set_editable(true); - parent_browse_button->set_disabled(false); + parent_search_button->set_disabled(false); + parent_browse_button->set_disabled(!can_inherit_from_file); internal->set_visible(_can_be_built_in()); internal_label->set_visible(_can_be_built_in()); if (is_path_valid) { @@ -590,6 +580,7 @@ void ScriptCreateDialog::_update_dialog() { // Script Loaded get_ok()->set_text(TTR("Load")); parent_name->set_editable(false); + parent_search_button->set_disabled(true); parent_browse_button->set_disabled(true); internal->set_disabled(!_can_be_built_in()); if (is_path_valid) { @@ -755,7 +746,6 @@ ScriptCreateDialog::ScriptCreateDialog() { internal->set_h_size_flags(0); internal->connect("pressed", this, "_built_in_pressed"); internal_label = memnew(Label(TTR("Built-in Script"))); - internal_label->set_text(TTR("Built-in Script")); internal_label->set_align(Label::ALIGN_RIGHT); gc->add_child(internal_label); gc->add_child(internal); diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h index c6dba78f56..61f87f5732 100644 --- a/editor/script_create_dialog.h +++ b/editor/script_create_dialog.h @@ -87,7 +87,8 @@ class ScriptCreateDialog : public ConfirmationDialog { void _path_entered(const String &p_path = String()); void _lang_changed(int l = 0); void _built_in_pressed(); - bool _validate(const String &p_string); + bool _validate_parent(const String &p_string); + bool _validate_class(const String &p_string); String _validate_path(const String &p_path, bool p_file_must_exist); void _class_name_changed(const String &p_name); void _parent_name_changed(const String &p_parent); diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index c3b62810f1..3b086c6316 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -727,20 +727,8 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da String tt = vs; switch (Performance::MonitorType((int)perf_items[i]->get_metadata(1))) { case Performance::MONITOR_TYPE_MEMORY: { - // for the time being, going above GBs is a bad sign. - String unit = "B"; - if ((int)v > 1073741824) { - unit = "GB"; - v /= 1073741824.0; - } else if ((int)v > 1048576) { - unit = "MB"; - v /= 1048576.0; - } else if ((int)v > 1024) { - unit = "KB"; - v /= 1024.0; - } - tt += " bytes"; - vs = String::num(v, 2) + " " + unit; + vs = String::humanize_size(v); + tt = vs; } break; case Performance::MONITOR_TYPE_TIME: { tt += " seconds"; diff --git a/main/main.cpp b/main/main.cpp index 14ef257526..7e4fdeeaa7 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -939,7 +939,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false); video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency/enabled", false); - video_mode.layered_splash = GLOBAL_DEF("display/window/per_pixel_transparency/splash", false); GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_allocation", 2); GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_allocation.mobile", 3); @@ -1021,6 +1020,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,120,1,or_greater")); Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5)); Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0)); ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater")); diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index a5b2238e6b..2171f27f96 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -116,9 +116,9 @@ protected: virtual void _validate_property(PropertyInfo &property) const; +public: Array get_meshes() const; -public: void set_operation(Operation p_operation); Operation get_operation() const; diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index 059c06c37c..50fdc8ab20 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -31,6 +31,8 @@ #include "texture_loader_dds.h" #include "core/os/file_access.h" +#define PF_FOURCC(s) (((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0])) + enum { DDS_MAGIC = 0x20534444, DDSD_CAPS = 0x00000001, @@ -51,6 +53,7 @@ enum DDSFormat { DDS_DXT5, DDS_ATI1, DDS_ATI2, + DDS_A2XY, DDS_BGRA8, DDS_BGR8, DDS_RGBA8, //flipped in dds @@ -74,9 +77,12 @@ struct DDSFormatInfo { }; static const DDSFormatInfo dds_format_info[DDS_MAX] = { - { "DXT1", true, false, 4, 8, Image::FORMAT_DXT1 }, - { "DXT3", true, false, 4, 16, Image::FORMAT_DXT3 }, - { "DXT5", true, false, 4, 16, Image::FORMAT_DXT5 }, + { "DXT1/BC1", true, false, 4, 8, Image::FORMAT_DXT1 }, + { "DXT3/BC2", true, false, 4, 16, Image::FORMAT_DXT3 }, + { "DXT5/BC3", true, false, 4, 16, Image::FORMAT_DXT5 }, + { "ATI1/BC4", true, false, 4, 8, Image::FORMAT_RGTC_R }, + { "ATI2/3DC/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG }, + { "A2XY/DXN/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG }, { "BGRA8", false, false, 1, 4, Image::FORMAT_RGBA8 }, { "BGR8", false, false, 1, 3, Image::FORMAT_RGB8 }, { "RGBA8", false, false, 1, 4, Image::FORMAT_RGBA8 }, @@ -158,22 +164,25 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, DDSFormat dds_format; - if (format_flags & DDPF_FOURCC && format_fourcc == 0x31545844) { //'1TXD' + if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT1")) { dds_format = DDS_DXT1; - } else if (format_flags & DDPF_FOURCC && format_fourcc == 0x33545844) { //'3TXD' + } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT3")) { dds_format = DDS_DXT3; - } else if (format_flags & DDPF_FOURCC && format_fourcc == 0x35545844) { //'5TXD' + } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT5")) { dds_format = DDS_DXT5; - } else if (format_flags & DDPF_FOURCC && format_fourcc == 0x31495441) { //'1ITA' + } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI1")) { dds_format = DDS_ATI1; - } else if (format_flags & DDPF_FOURCC && format_fourcc == 0x32495441) { //'2ITA' + } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI2")) { dds_format = DDS_ATI2; + } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("A2XY")) { + + dds_format = DDS_A2XY; } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 511069222b..ec3e72eef7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1995,7 +1995,6 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to } } - ERR_FAIL_V(op); } break; default: { return p_node; diff --git a/modules/recast/navigation_mesh_editor_plugin.cpp b/modules/recast/navigation_mesh_editor_plugin.cpp index eadc11fcee..9f30806925 100644 --- a/modules/recast/navigation_mesh_editor_plugin.cpp +++ b/modules/recast/navigation_mesh_editor_plugin.cpp @@ -67,9 +67,7 @@ void NavigationMeshEditor::_bake_pressed() { EditorNavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh()); EditorNavigationMeshGenerator::get_singleton()->bake(node->get_navigation_mesh(), node); - if (node) { - node->update_gizmo(); - } + node->update_gizmo(); } void NavigationMeshEditor::_clear_pressed() { diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp index 0cac07e3e7..14467dc5c7 100644 --- a/modules/recast/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -45,6 +45,10 @@ #include "scene/resources/shape.h" #include "scene/resources/sphere_shape.h" +#ifdef MODULE_CSG_ENABLED +#include "modules/csg/csg_shape.h" +#endif + EditorNavigationMeshGenerator *EditorNavigationMeshGenerator::singleton = NULL; void EditorNavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies) { @@ -134,6 +138,20 @@ void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_tran } } +#ifdef MODULE_CSG_ENABLED + if (Object::cast_to<CSGShape>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { + + CSGShape *csg_shape = Object::cast_to<CSGShape>(p_node); + Array meshes = csg_shape->get_meshes(); + if (!meshes.empty()) { + Ref<Mesh> mesh = meshes[1]; + if (mesh.is_valid()) { + _add_mesh(mesh, p_accumulated_transform * csg_shape->get_transform(), p_verticies, p_indices); + } + } + } +#endif + if (Object::cast_to<StaticBody>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES) { StaticBody *static_body = Object::cast_to<StaticBody>(p_node); diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index 6485c95360..3670edc9ea 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -413,10 +413,11 @@ void VideoStreamPlaybackWebm::delete_pointers() { if (audio_frame) memdelete(audio_frame); - for (int i = 0; i < video_frames_capacity; ++i) - memdelete(video_frames[i]); - if (video_frames) + if (video_frames) { + for (int i = 0; i < video_frames_capacity; ++i) + memdelete(video_frames[i]); memfree(video_frames); + } if (video) memdelete(video); diff --git a/modules/webrtc/config.py b/modules/webrtc/config.py index 2e3a18ad0e..48b4c33c5d 100644 --- a/modules/webrtc/config.py +++ b/modules/webrtc/config.py @@ -7,7 +7,8 @@ def configure(env): def get_doc_classes(): return [ "WebRTCPeerConnection", - "WebRTCDataChannel" + "WebRTCDataChannel", + "WebRTCMultiplayer" ] def get_doc_path(): diff --git a/modules/webrtc/doc_classes/WebRTCDataChannel.xml b/modules/webrtc/doc_classes/WebRTCDataChannel.xml index dcc14d4ddb..f03ae864f8 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannel.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannel.xml @@ -11,85 +11,106 @@ <return type="void"> </return> <description> + Closes this data channel, notifying the other peer. </description> </method> <method name="get_id" qualifiers="const"> <return type="int"> </return> <description> + Returns the id assigned to this channel during creation (or auto-assigned during negotiation). + If the channel is not negotiated out-of-band the id will only be available after the connection is established (will return [code]65535[/code] until then). </description> </method> <method name="get_label" qualifiers="const"> <return type="String"> </return> <description> + Returns the label assigned to this channel during creation. </description> </method> <method name="get_max_packet_life_time" qualifiers="const"> <return type="int"> </return> <description> + Returns the maxPacketLifeTime value assigned to this channel during creation. + Will be [code]65535[/code] if not specified. </description> </method> <method name="get_max_retransmits" qualifiers="const"> <return type="int"> </return> <description> + Returns the maxRetransmits value assigned to this channel during creation. + Will be [code]65535[/code] if not specified. </description> </method> <method name="get_protocol" qualifiers="const"> <return type="String"> </return> <description> + Returns the sub-proctocol assigned to this channel during creation. An empty string if not specified. </description> </method> <method name="get_ready_state" qualifiers="const"> <return type="int" enum="WebRTCDataChannel.ChannelState"> </return> <description> + Returns the current state of this channel, see [enum WebRTCDataChannel.ChannelState]. </description> </method> <method name="is_negotiated" qualifiers="const"> <return type="bool"> </return> <description> + Returns [code]true[/code] if this channel was created with out-of-band configuration. </description> </method> <method name="is_ordered" qualifiers="const"> <return type="bool"> </return> <description> + Returns [code]true[/code] if this channel was created with ordering enabled (default). </description> </method> <method name="poll"> <return type="int" enum="Error"> </return> <description> + Reserved, but not used for now. </description> </method> <method name="was_string_packet" qualifiers="const"> <return type="bool"> </return> <description> + Returns [code]true[/code] if the last received packet was transfered as text. See [property write_mode]. </description> </method> </methods> <members> <member name="write_mode" type="int" setter="set_write_mode" getter="get_write_mode" enum="WebRTCDataChannel.WriteMode"> + The transfer mode to use when sending outgoing packet. Either text or binary. </member> </members> <constants> <constant name="WRITE_MODE_TEXT" value="0" enum="WriteMode"> + Tells the channel to send data over this channel as text. An external peer (non-godot) would receive this as a string. </constant> <constant name="WRITE_MODE_BINARY" value="1" enum="WriteMode"> + Tells the channel to send data over this channel as binary. An external peer (non-godot) would receive this as arraybuffer or blob. </constant> <constant name="STATE_CONNECTING" value="0" enum="ChannelState"> + The channel was created, but it's still trying to connect. </constant> <constant name="STATE_OPEN" value="1" enum="ChannelState"> + The channel is currently open, and data can flow over it. </constant> <constant name="STATE_CLOSING" value="2" enum="ChannelState"> + The channel is being closed, no new messages will be accepted, but those already in queue will be flushed. </constant> <constant name="STATE_CLOSED" value="3" enum="ChannelState"> + The channel was closed, or connection failed. </constant> </constants> </class> diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml new file mode 100644 index 0000000000..2b0622fffa --- /dev/null +++ b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebRTCMultiplayer" inherits="NetworkedMultiplayerPeer" category="Core" version="3.2"> + <brief_description> + A simple interface to create a peer-to-peer mesh network composed of [WebRTCPeerConnection] that is compatible with the [MultiplayerAPI]. + </brief_description> + <description> + This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.network_peer]. + You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections. + [signal NetworkedMultiplayerPeer.connection_succeeded] and [signal NetworkedMultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [NetworkedMultiplayerPeer]. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_peer"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="peer" type="WebRTCPeerConnection"> + </argument> + <argument index="1" name="peer_id" type="int"> + </argument> + <argument index="2" name="unreliable_lifetime" type="int" default="1"> + </argument> + <description> + Add a new peer to the mesh with the given [code]peer_id[/code]. The [WebRTCPeerConnection] must be in state [constant WebRTCPeerConnection.STATE_NEW]. + Three channels will be created for reliable, unreliable, and ordered transport. The value of [code]unreliable_lifetime[/code] will be passed to the [code]maxPacketLifetime[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]). + </description> + </method> + <method name="close"> + <return type="void"> + </return> + <description> + Close all the add peer connections and channels, freeing all resources. + </description> + </method> + <method name="get_peer"> + <return type="Dictionary"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Return a dictionary representation of the peer with given [code]peer_id[/code] with three keys. [code]connection[/code] containing the [WebRTCPeerConnection] to this peer, [code]channels[/code] an array of three [WebRTCDataChannel], and [code]connected[/code] a boolean representing if the peer connection is currently connected (all three channels are open). + </description> + </method> + <method name="get_peers"> + <return type="Dictionary"> + </return> + <description> + Returns a dictionary which keys are the peer ids and values the peer representation as in [method get_peer] + </description> + </method> + <method name="has_peer"> + <return type="bool"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Returns [code]true[/code] if the given [code]peer_id[/code] is in the peers map (it might not be connected though). + </description> + </method> + <method name="initialize"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <argument index="1" name="server_compatibility" type="bool" default="false"> + </argument> + <description> + Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). + If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant NetworkedMultiplayerPeer.CONNECTION_CONNECTED] and [signal NetworkedMultiplayerPeer.connection_succeeded] will not be emitted. + If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal NetworkedMultiplayerPeer.peer_connected] signals until a peer with id [constant NetworkedMultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal NetworkedMultiplayerPeer.connection_succeeded]. After that the signal [signal NetworkedMultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal NetworkedMultiplayerPeer.server_disconnected] will be emitted and state will become [constant NetworkedMultiplayerPeer.CONNECTION_CONNECTED]. + </description> + </method> + <method name="remove_peer"> + <return type="void"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Remove the peer with given [code]peer_id[/code] from the mesh. If the peer was connected, and [signal NetworkedMultiplayerPeer.peer_connected] was emitted for it, then [signal NetworkedMultiplayerPeer.peer_disconnected] will be emitted. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 8b14c60deb..aa2c856b6e 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -1,8 +1,17 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="WebRTCPeerConnection" inherits="Reference" category="Core" version="3.2"> <brief_description> + Interface to a WebRTC peer connection. </brief_description> <description> + A WebRTC connection between the local computer and a remote peer. Provides an interface to connect, maintain and monitor the connection. + + Setting up a WebRTC connection between two peers from now on) may not seem a trival task, but it can be broken down into 3 main steps: + - The peer that wants to initiate the connection ([code]A[/code] from now on) creates an offer and send it to the other peer ([code]B[/code] from now on). + - [code]B[/code] receives the offer, generate and answer, and sends it to [code]B[/code]). + - [code]A[/code] and [code]B[/code] then generates and exchange ICE candiates with each other. + + After these steps, the connection should become connected. Keep on reading or look into the tutorial for more information. </description> <tutorials> </tutorials> @@ -17,12 +26,14 @@ <argument index="2" name="name" type="String"> </argument> <description> + Add an ice candidate generated by a remote peer (and received over the signaling server). See [signal ice_candidate_created]. </description> </method> <method name="close"> <return type="void"> </return> <description> + Close the peer connection and all data channels associated with it. Note, you cannot reuse this object for a new connection unless you call [method initialize]. </description> </method> <method name="create_data_channel"> @@ -35,18 +46,38 @@ }"> </argument> <description> + Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [code]label[/code] and optionally configured via the [code]options[/code] dictionary. This method can only be called when the connection is in state [constant STATE_NEW]. + There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]negotiated[/code] option set to [code]true[/code]. + Valid [code]options[/code] are: + [code] + { + "negotiated": true, # When set to true (default off), means the channel is negotiated out of band. "id" must be set too. data_channel_received will not be called. + "id": 1, # When "negotiated" is true this value must also be set to the same value on both peer. + + # Only one of maxRetransmits and maxPacketLifeTime can be specified, not both. They make the channel unreliable (but also better at real time). + "maxRetransmits": 1, # Specify the maximum number of attempt the peer will make to retransmits packets if they are not acknowledged. + "maxPacketLifeTime": 100, # Specify the maximum amount of time before giving up retransmitions of unacknowledged packets (in milliseconds). + "ordered": true, # When in unreliable mode (i.e. either "maxRetransmits" or "maxPacketLifetime" is set), "ordered" (true by default) specify if packet ordering is to be enforced. + + "protocol": "my-custom-protocol", # A custom sub-protocol string for this channel. + } + [/code] + NOTE: You must keep a reference to channels created this way, or it will be closed. </description> </method> <method name="create_offer"> <return type="int" enum="Error"> </return> <description> + Creates a new SDP offer to start a WebRTC connection with a remote peer. At least one [WebRTCDataChannel] must have been created before calling this method. + If this functions returns [code]OK[/code], [signal session_description_created] will be called when the session is ready to be sent. </description> </method> <method name="get_connection_state" qualifiers="const"> <return type="int" enum="WebRTCPeerConnection.ConnectionState"> </return> <description> + Returns the connection state. See [enum ConnectionState]. </description> </method> <method name="initialize"> @@ -57,12 +88,29 @@ }"> </argument> <description> + Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [code]options[/code] can be passed to configure the peer connection. + Valid [code]options[/code] are: + [code] + { + "iceServers": [ + { + "urls": [ "stun:stun.example.com:3478" ], # One or more STUN servers. + }, + { + "urls": [ "turn:turn.example.com:3478" ], # One or more TURN servers. + "username": "a_username", # Optional username for the TURN server. + "credentials": "a_password", # Optional password for the TURN server. + } + ] + } + [/code] </description> </method> <method name="poll"> <return type="int" enum="Error"> </return> <description> + Call this method frequently (e.g. in [method Node._process] or [method Node._fixed_process]) to properly receive signals. </description> </method> <method name="set_local_description"> @@ -73,6 +121,8 @@ <argument index="1" name="sdp" type="String"> </argument> <description> + Sets the SDP description of the local peer. This should be called in response to [signal session_description_created]. + If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created]. </description> </method> <method name="set_remote_description"> @@ -83,6 +133,9 @@ <argument index="1" name="sdp" type="String"> </argument> <description> + Sets the SDP description of the remote peer. This should be called with the values generated by a remote peer and received over the signaling server. + If [code]type[/code] is [code]offer[/code] the peer will emit [signal session_description_created] with the appropriate answer. + If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created]. </description> </method> </methods> @@ -91,6 +144,8 @@ <argument index="0" name="channel" type="Object"> </argument> <description> + Emitted when a new in-band channel is received, i.e. when the channel was created with [code]negotiated: false[/code] (default). + The object will be an instance of [WebRTCDataChannel]. You must keep a reference of it or it will be closed automatically. See [method create_data_channel] </description> </signal> <signal name="ice_candidate_created"> @@ -101,6 +156,7 @@ <argument index="2" name="name" type="String"> </argument> <description> + Emitted when a new ICE candidate has been created. The three parameters are meant to be passed to the remote peer over the signaling server. </description> </signal> <signal name="session_description_created"> @@ -109,21 +165,28 @@ <argument index="1" name="sdp" type="String"> </argument> <description> + Emitted after a successful call to [method create_offer] or [method set_remote_description] (when it generates an answer). The parameters are meant to be passed to [method set_local_description] on this object, and sent to the remote peer over the signaling server. </description> </signal> </signals> <constants> <constant name="STATE_NEW" value="0" enum="ConnectionState"> + The connection is new, data channels and an offer can be created in this state. </constant> <constant name="STATE_CONNECTING" value="1" enum="ConnectionState"> + The peer is connecting, ICE is in progress, non of the transports has failed. </constant> <constant name="STATE_CONNECTED" value="2" enum="ConnectionState"> + The peer is connected, all ICE transports are connected. </constant> <constant name="STATE_DISCONNECTED" value="3" enum="ConnectionState"> + At least one ICE transport is disconnected. </constant> <constant name="STATE_FAILED" value="4" enum="ConnectionState"> + One or more of the ICE transports failed. </constant> <constant name="STATE_CLOSED" value="5" enum="ConnectionState"> + The peer connection is closed (after calling [method close] for example). </constant> </constants> </class> diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 44e072cc89..58b68d926b 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -40,6 +40,7 @@ #include "webrtc_data_channel_gdnative.h" #include "webrtc_peer_connection_gdnative.h" #endif +#include "webrtc_multiplayer.h" void register_webrtc_types() { #ifdef JAVASCRIPT_ENABLED @@ -54,6 +55,7 @@ void register_webrtc_types() { ClassDB::register_class<WebRTCDataChannelGDNative>(); #endif ClassDB::register_virtual_class<WebRTCDataChannel>(); + ClassDB::register_class<WebRTCMultiplayer>(); } void unregister_webrtc_types() {} diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 2e7c64aa72..ce2fada634 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -205,30 +205,45 @@ String WebRTCDataChannelJS::get_label() const { } /* clang-format off */ -#define _JS_GET(PROP) \ +#define _JS_GET(PROP, DEF) \ EM_ASM_INT({ \ var dict = Module.IDHandler.get($0); \ if (!dict || !dict["channel"]) { \ - return 0; \ - }; \ - return dict["channel"].PROP; \ + return DEF; \ + } \ + var out = dict["channel"].PROP; \ + return out === null ? DEF : out; \ }, _js_id) /* clang-format on */ bool WebRTCDataChannelJS::is_ordered() const { - return _JS_GET(ordered); + return _JS_GET(ordered, true); } int WebRTCDataChannelJS::get_id() const { - return _JS_GET(id); + return _JS_GET(id, 65535); } int WebRTCDataChannelJS::get_max_packet_life_time() const { - return _JS_GET(maxPacketLifeTime); + // Can't use macro, webkit workaround. + /* clang-format off */ + return EM_ASM_INT({ + var dict = Module.IDHandler.get($0); + if (!dict || !dict["channel"]) { + return 65535; + } + if (dict["channel"].maxRetransmitTime !== undefined) { + // Guess someone didn't appreciate the standardization process. + return dict["channel"].maxRetransmitTime; + } + var out = dict["channel"].maxPacketLifeTime; + return out === null ? 65535 : out; + }, _js_id); + /* clang-format on */ } int WebRTCDataChannelJS::get_max_retransmits() const { - return _JS_GET(maxRetransmits); + return _JS_GET(maxRetransmits, 65535); } String WebRTCDataChannelJS::get_protocol() const { @@ -236,7 +251,7 @@ String WebRTCDataChannelJS::get_protocol() const { } bool WebRTCDataChannelJS::is_negotiated() const { - return _JS_GET(negotiated); + return _JS_GET(negotiated, false); } WebRTCDataChannelJS::WebRTCDataChannelJS() { diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp new file mode 100644 index 0000000000..17dafff93a --- /dev/null +++ b/modules/webrtc/webrtc_multiplayer.cpp @@ -0,0 +1,384 @@ +/*************************************************************************/ +/* webrtc_multiplayer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "webrtc_multiplayer.h" + +#include "core/io/marshalls.h" +#include "core/os/os.h" + +void WebRTCMultiplayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility"), &WebRTCMultiplayer::initialize, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_peer", "peer", "peer_id", "unreliable_lifetime"), &WebRTCMultiplayer::add_peer, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("remove_peer", "peer_id"), &WebRTCMultiplayer::remove_peer); + ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayer::has_peer); + ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebRTCMultiplayer::get_peer); + ClassDB::bind_method(D_METHOD("get_peers"), &WebRTCMultiplayer::get_peers); + ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayer::close); +} + +void WebRTCMultiplayer::set_transfer_mode(TransferMode p_mode) { + transfer_mode = p_mode; +} + +NetworkedMultiplayerPeer::TransferMode WebRTCMultiplayer::get_transfer_mode() const { + return transfer_mode; +} + +void WebRTCMultiplayer::set_target_peer(int p_peer_id) { + target_peer = p_peer_id; +} + +/* Returns the ID of the NetworkedMultiplayerPeer who sent the most recent packet: */ +int WebRTCMultiplayer::get_packet_peer() const { + return next_packet_peer; +} + +bool WebRTCMultiplayer::is_server() const { + return unique_id == TARGET_PEER_SERVER; +} + +void WebRTCMultiplayer::poll() { + if (peer_map.size() == 0) + return; + + List<int> remove; + List<int> add; + for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { + Ref<ConnectedPeer> peer = E->get(); + peer->connection->poll(); + // Check peer state + switch (peer->connection->get_connection_state()) { + case WebRTCPeerConnection::STATE_NEW: + case WebRTCPeerConnection::STATE_CONNECTING: + // Go to next peer, not ready yet. + continue; + case WebRTCPeerConnection::STATE_CONNECTED: + // Good to go, go ahead and check channel state. + break; + default: + // Peer is closed or in error state. Got to next peer. + remove.push_back(E->key()); + continue; + } + // Check channels state + int ready = 0; + for (List<Ref<WebRTCDataChannel> >::Element *C = peer->channels.front(); C && C->get().is_valid(); C = C->next()) { + Ref<WebRTCDataChannel> ch = C->get(); + switch (ch->get_ready_state()) { + case WebRTCDataChannel::STATE_CONNECTING: + continue; + case WebRTCDataChannel::STATE_OPEN: + ready++; + continue; + default: + // Channel was closed or in error state, remove peer id. + remove.push_back(E->key()); + } + // We got a closed channel break out, the peer will be removed. + break; + } + // This peer has newly connected, and all channels are now open. + if (ready == peer->channels.size() && !peer->connected) { + peer->connected = true; + add.push_back(E->key()); + } + } + // Remove disconnected peers + for (List<int>::Element *E = remove.front(); E; E = E->next()) { + remove_peer(E->get()); + if (next_packet_peer == E->get()) + next_packet_peer = 0; + } + // Signal newly connected peers + for (List<int>::Element *E = add.front(); E; E = E->next()) { + // Already connected to server: simply notify new peer. + // NOTE: Mesh is always connected. + if (connection_status == CONNECTION_CONNECTED) + emit_signal("peer_connected", E->get()); + + // Server emulation mode suppresses peer_conencted until server connects. + if (server_compat && E->get() == TARGET_PEER_SERVER) { + // Server connected. + connection_status = CONNECTION_CONNECTED; + emit_signal("peer_connected", TARGET_PEER_SERVER); + emit_signal("connection_succeeded"); + // Notify of all previously connected peers + for (Map<int, Ref<ConnectedPeer> >::Element *F = peer_map.front(); F; F = F->next()) { + if (F->key() != 1 && F->get()->connected) + emit_signal("peer_connected", F->key()); + } + break; // Because we already notified of all newly added peers. + } + } + // Fetch next packet + if (next_packet_peer == 0) + _find_next_peer(); +} + +void WebRTCMultiplayer::_find_next_peer() { + Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.find(next_packet_peer); + if (E) E = E->next(); + // After last. + while (E) { + for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { + if (F->get()->get_available_packet_count()) { + next_packet_peer = E->key(); + return; + } + } + E = E->next(); + } + E = peer_map.front(); + // Before last + while (E) { + for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { + if (F->get()->get_available_packet_count()) { + next_packet_peer = E->key(); + return; + } + } + if (E->key() == (int)next_packet_peer) + break; + E = E->next(); + } + // No packet found + next_packet_peer = 0; +} + +void WebRTCMultiplayer::set_refuse_new_connections(bool p_enable) { + refuse_connections = p_enable; +} + +bool WebRTCMultiplayer::is_refusing_new_connections() const { + return refuse_connections; +} + +NetworkedMultiplayerPeer::ConnectionStatus WebRTCMultiplayer::get_connection_status() const { + return connection_status; +} + +Error WebRTCMultiplayer::initialize(int p_self_id, bool p_server_compat) { + ERR_FAIL_COND_V(p_self_id < 0 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); + unique_id = p_self_id; + server_compat = p_server_compat; + + // Mesh and server are always connected + if (!server_compat || p_self_id == 1) + connection_status = CONNECTION_CONNECTED; + else + connection_status = CONNECTION_CONNECTING; + return OK; +} + +int WebRTCMultiplayer::get_unique_id() const { + ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, 1); + return unique_id; +} + +void WebRTCMultiplayer::_peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict) { + Array channels; + for (List<Ref<WebRTCDataChannel> >::Element *F = p_connected_peer->channels.front(); F; F = F->next()) { + channels.push_back(F->get()); + } + r_dict["connection"] = p_connected_peer->connection; + r_dict["connected"] = p_connected_peer->connected; + r_dict["channels"] = channels; +} + +bool WebRTCMultiplayer::has_peer(int p_peer_id) { + return peer_map.has(p_peer_id); +} + +Dictionary WebRTCMultiplayer::get_peer(int p_peer_id) { + ERR_FAIL_COND_V(!peer_map.has(p_peer_id), Dictionary()); + Dictionary out; + _peer_to_dict(peer_map[p_peer_id], out); + return out; +} + +Dictionary WebRTCMultiplayer::get_peers() { + Dictionary out; + for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { + Dictionary d; + _peer_to_dict(E->get(), d); + out[E->key()] = d; + } + return out; +} + +Error WebRTCMultiplayer::add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime) { + ERR_FAIL_COND_V(p_peer_id < 0 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_unreliable_lifetime < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(refuse_connections, ERR_UNAUTHORIZED); + // Peer must be valid, and in new state (to create data channels) + ERR_FAIL_COND_V(!p_peer.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_peer->get_connection_state() != WebRTCPeerConnection::STATE_NEW, ERR_INVALID_PARAMETER); + + Ref<ConnectedPeer> peer = memnew(ConnectedPeer); + peer->connection = p_peer; + + // Initialize data channels + Dictionary cfg; + cfg["negotiated"] = true; + cfg["ordered"] = true; + + cfg["id"] = 1; + peer->channels[CH_RELIABLE] = p_peer->create_data_channel("reliable", cfg); + ERR_FAIL_COND_V(!peer->channels[CH_RELIABLE].is_valid(), FAILED); + + cfg["id"] = 2; + cfg["maxPacketLifetime"] = p_unreliable_lifetime; + peer->channels[CH_ORDERED] = p_peer->create_data_channel("ordered", cfg); + ERR_FAIL_COND_V(!peer->channels[CH_ORDERED].is_valid(), FAILED); + + cfg["id"] = 3; + cfg["ordered"] = false; + peer->channels[CH_UNRELIABLE] = p_peer->create_data_channel("unreliable", cfg); + ERR_FAIL_COND_V(!peer->channels[CH_UNRELIABLE].is_valid(), FAILED); + + peer_map[p_peer_id] = peer; // add the new peer connection to the peer_map + + return OK; +} + +void WebRTCMultiplayer::remove_peer(int p_peer_id) { + ERR_FAIL_COND(!peer_map.has(p_peer_id)); + Ref<ConnectedPeer> peer = peer_map[p_peer_id]; + peer_map.erase(p_peer_id); + if (peer->connected) { + peer->connected = false; + emit_signal("peer_disconnected", p_peer_id); + if (server_compat && p_peer_id == TARGET_PEER_SERVER) { + emit_signal("server_disconnected"); + connection_status = CONNECTION_DISCONNECTED; + } + } +} + +Error WebRTCMultiplayer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + // Peer not available + if (next_packet_peer == 0 || !peer_map.has(next_packet_peer)) { + _find_next_peer(); + ERR_FAIL_V(ERR_UNAVAILABLE); + } + for (List<Ref<WebRTCDataChannel> >::Element *E = peer_map[next_packet_peer]->channels.front(); E; E = E->next()) { + if (E->get()->get_available_packet_count()) { + Error err = E->get()->get_packet(r_buffer, r_buffer_size); + _find_next_peer(); + return err; + } + } + // Channels for that peer were empty. Bug? + _find_next_peer(); + ERR_FAIL_V(ERR_BUG); +} + +Error WebRTCMultiplayer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, ERR_UNCONFIGURED); + + int ch = CH_RELIABLE; + switch (transfer_mode) { + case TRANSFER_MODE_RELIABLE: + ch = CH_RELIABLE; + break; + case TRANSFER_MODE_UNRELIABLE_ORDERED: + ch = CH_ORDERED; + break; + case TRANSFER_MODE_UNRELIABLE: + ch = CH_UNRELIABLE; + break; + } + + Map<int, Ref<ConnectedPeer> >::Element *E = NULL; + + if (target_peer > 0) { + + E = peer_map.find(target_peer); + if (!E) { + ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG); + ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG); + return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); + + } else { + int exclude = -target_peer; + + for (Map<int, Ref<ConnectedPeer> >::Element *F = peer_map.front(); F; F = F->next()) { + + // Exclude packet. If target_peer == 0 then don't exclude any packets + if (target_peer != 0 && F->key() == exclude) + continue; + + ERR_CONTINUE(F->value()->channels.size() <= ch || !F->value()->channels[ch].is_valid()); + F->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); + } + } + return OK; +} + +int WebRTCMultiplayer::get_available_packet_count() const { + if (next_packet_peer == 0) + return 0; // To be sure next call to get_packet works if size > 0 . + int size = 0; + for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { + for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { + size += F->get()->get_available_packet_count(); + } + } + return size; +} + +int WebRTCMultiplayer::get_max_packet_size() const { + return 1200; +} + +void WebRTCMultiplayer::close() { + peer_map.clear(); + unique_id = 0; + next_packet_peer = 0; + target_peer = 0; + connection_status = CONNECTION_DISCONNECTED; +} + +WebRTCMultiplayer::WebRTCMultiplayer() { + unique_id = 0; + next_packet_peer = 0; + target_peer = 0; + transfer_mode = TRANSFER_MODE_RELIABLE; + refuse_connections = false; + connection_status = CONNECTION_DISCONNECTED; + server_compat = false; +} + +WebRTCMultiplayer::~WebRTCMultiplayer() { + close(); +} diff --git a/modules/webrtc/webrtc_multiplayer.h b/modules/webrtc/webrtc_multiplayer.h new file mode 100644 index 0000000000..82bbfd4f68 --- /dev/null +++ b/modules/webrtc/webrtc_multiplayer.h @@ -0,0 +1,116 @@ +/*************************************************************************/ +/* webrtc_multiplayer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 WEBRTC_MULTIPLAYER_H +#define WEBRTC_MULTIPLAYER_H + +#include "core/io/networked_multiplayer_peer.h" +#include "webrtc_peer_connection.h" + +class WebRTCMultiplayer : public NetworkedMultiplayerPeer { + + GDCLASS(WebRTCMultiplayer, NetworkedMultiplayerPeer); + +protected: + static void _bind_methods(); + +private: + enum { + CH_RELIABLE = 0, + CH_ORDERED = 1, + CH_UNRELIABLE = 2, + CH_RESERVED_MAX = 3 + }; + + class ConnectedPeer : public Reference { + + public: + Ref<WebRTCPeerConnection> connection; + List<Ref<WebRTCDataChannel> > channels; + bool connected; + + ConnectedPeer() { + connected = false; + for (int i = 0; i < CH_RESERVED_MAX; i++) + channels.push_front(Ref<WebRTCDataChannel>()); + } + }; + + uint32_t unique_id; + int target_peer; + int client_count; + bool refuse_connections; + ConnectionStatus connection_status; + TransferMode transfer_mode; + int next_packet_peer; + bool server_compat; + + Map<int, Ref<ConnectedPeer> > peer_map; + + void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict); + void _find_next_peer(); + +public: + WebRTCMultiplayer(); + ~WebRTCMultiplayer(); + + Error initialize(int p_self_id, bool p_server_compat = false); + Error add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime = 1); + void remove_peer(int p_peer_id); + bool has_peer(int p_peer_id); + Dictionary get_peer(int p_peer_id); + Dictionary get_peers(); + void close(); + + // PacketPeer + Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); ///< buffer is GONE after next get_packet + Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + int get_available_packet_count() const; + int get_max_packet_size() const; + + // NetworkedMultiplayerPeer + void set_transfer_mode(TransferMode p_mode); + TransferMode get_transfer_mode() const; + void set_target_peer(int p_peer_id); + + int get_unique_id() const; + int get_packet_peer() const; + + bool is_server() const; + + void poll(); + + void set_refuse_new_connections(bool p_enable); + bool is_refusing_new_connections() const; + + ConnectionStatus get_connection_status() const; +}; + +#endif diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 567d24de3e..bdc17c7124 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -1002,7 +1002,7 @@ static const _KeyCodeMap _keycodes[55] = { { '/', KEY_SLASH } }; -static int remapKey(unsigned int key) { +static int remapKey(unsigned int key, unsigned int state) { if (isNumpadKey(key)) return translateKey(key); @@ -1024,7 +1024,7 @@ static int remapKey(unsigned int key) { OSStatus err = UCKeyTranslate(keyboardLayout, key, kUCKeyActionDisplay, - 0, + (state >> 8) & 0xFF, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &keysDown, @@ -1051,7 +1051,7 @@ static int remapKey(unsigned int key) { NSString *characters = [event characters]; NSUInteger length = [characters length]; - if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode]))) { + if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { // Fallback unicode character handler used if IME is not active for (NSUInteger i = 0; i < length; i++) { OS_OSX::KeyEvent ke; @@ -1059,7 +1059,7 @@ static int remapKey(unsigned int key) { ke.osx_state = [event modifierFlags]; ke.pressed = true; ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode]); + ke.scancode = remapKey([event keyCode], [event modifierFlags]); ke.raw = true; ke.unicode = [characters characterAtIndex:i]; @@ -1071,7 +1071,7 @@ static int remapKey(unsigned int key) { ke.osx_state = [event modifierFlags]; ke.pressed = true; ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode]); + ke.scancode = remapKey([event keyCode], [event modifierFlags]); ke.raw = false; ke.unicode = 0; @@ -1129,7 +1129,7 @@ static int remapKey(unsigned int key) { } ke.osx_state = mod; - ke.scancode = remapKey(key); + ke.scancode = remapKey(key, mod); ke.unicode = 0; push_to_key_event_buffer(ke); @@ -1144,14 +1144,14 @@ static int remapKey(unsigned int key) { NSUInteger length = [characters length]; // Fallback unicode character handler used if IME is not active - if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode]))) { + if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { for (NSUInteger i = 0; i < length; i++) { OS_OSX::KeyEvent ke; ke.osx_state = [event modifierFlags]; ke.pressed = false; ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode]); + ke.scancode = remapKey([event keyCode], [event modifierFlags]); ke.raw = true; ke.unicode = [characters characterAtIndex:i]; @@ -1163,7 +1163,7 @@ static int remapKey(unsigned int key) { ke.osx_state = [event modifierFlags]; ke.pressed = false; ke.echo = [event isARepeat]; - ke.scancode = remapKey([event keyCode]); + ke.scancode = remapKey([event keyCode], [event modifierFlags]); ke.raw = true; ke.unicode = 0; @@ -1551,7 +1551,7 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a restore_rect = Rect2(get_window_position(), get_window_size()); - if (p_desired.layered_splash) { + if (p_desired.layered) { set_window_per_pixel_transparency_enabled(true); } return OK; diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 141ab96370..4a72d07adc 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -138,8 +138,8 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio EditorExportPlatformPC::get_export_options(r_options); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), "")); diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc index f2dca10d55..1fa8957f15 100644 --- a/platform/windows/godot_res.rc +++ b/platform/windows/godot_res.rc @@ -21,7 +21,7 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Godot Engine" - VALUE "FileDescription", VERSION_NAME " Editor" + VALUE "FileDescription", VERSION_NAME VALUE "FileVersion", VERSION_NUMBER VALUE "ProductName", VERSION_NAME VALUE "Licence", "MIT" diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 6df2ad4821..4cd637a4b2 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1381,7 +1381,7 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int SetFocus(hWnd); // Sets Keyboard Focus To } - if (p_desired.layered_splash) { + if (p_desired.layered) { set_window_per_pixel_transparency_enabled(true); } diff --git a/platform/x11/joypad_linux.cpp b/platform/x11/joypad_linux.cpp index c4dd8fe0e0..3e9e8033e8 100644 --- a/platform/x11/joypad_linux.cpp +++ b/platform/x11/joypad_linux.cpp @@ -444,10 +444,10 @@ InputDefault::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int jx.min = -1; if (p_value < 0) { jx.value = (float)-p_value / min; + } else { + jx.value = (float)p_value / max; } - jx.value = (float)p_value / max; - } - if (min == 0) { + } else if (min == 0) { jx.min = 0; jx.value = 0.0f + (float)p_value / max; } diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 494ae41220..510f3d6114 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -593,7 +593,7 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a power_manager = memnew(PowerX11); - if (p_desired.layered_splash) { + if (p_desired.layered) { set_window_per_pixel_transparency_enabled(true); } diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 11846654c5..a0d74dd283 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -29,10 +29,11 @@ /*************************************************************************/ #include "camera_2d.h" + +#include "core/engine.h" #include "core/math/math_funcs.h" #include "scene/scene_string_names.h" #include "servers/visual_server.h" -#include <editor/editor_node.h> void Camera2D::_update_scroll() { @@ -44,15 +45,16 @@ void Camera2D::_update_scroll() { return; } + if (!viewport) + return; + if (current) { ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id)); Transform2D xform = get_camera_transform(); - if (viewport) { - viewport->set_canvas_transform(xform); - } + viewport->set_canvas_transform(xform); Size2 screen_size = viewport->get_visible_rect().size; Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2()); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 2b1009a2d1..536a05dceb 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -29,8 +29,9 @@ /*************************************************************************/ #include "cpu_particles_2d.h" -#include "particles_2d.h" + #include "scene/2d/canvas_item.h" +#include "scene/2d/particles_2d.h" #include "scene/resources/particles_material.h" #include "servers/visual_server.h" @@ -324,9 +325,9 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv case PARAM_ANGULAR_VELOCITY: { _adjust_curve_range(p_curve, -360, 360); } break; - /*case PARAM_ORBIT_VELOCITY: { + case PARAM_ORBIT_VELOCITY: { _adjust_curve_range(p_curve, -500, 500); - } break;*/ + } break; case PARAM_LINEAR_ACCEL: { _adjust_curve_range(p_curve, -200, 200); } break; @@ -489,12 +490,6 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = 0; } - - /* - if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) { - property.usage = 0; - } - */ } static uint32_t idhash(uint32_t x) { @@ -695,16 +690,12 @@ void CPUParticles2D::_particles_process(float p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]); } - /* - float tex_orbit_velocity = 0.0; - if (flags[FLAG_DISABLE_Z]) { - - if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) { - tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]); - } + float tex_orbit_velocity = 0.0; + if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(p.custom[1]); } -*/ + float tex_angular_velocity = 0.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]); @@ -759,18 +750,15 @@ void CPUParticles2D::_particles_process(float p_delta) { //apply attractor forces p.velocity += force * local_delta; //orbit velocity -#if 0 - if (flags[FLAG_DISABLE_Z]) { - - float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random); - if (orbit_amount != 0.0) { - float ang = orbit_amount * DELTA * pi * 2.0; - mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang))); - TRANSFORM[3].xy -= diff.xy; - TRANSFORM[3].xy += rot * diff.xy; - } + float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + if (orbit_amount != 0.0) { + float ang = orbit_amount * local_delta * Math_PI * 2.0; + // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, + // but we use -ang here to reproduce its behavior. + Transform2D rot = Transform2D(-ang, Vector2()); + p.transform[2] -= diff; + p.transform[2] += rot.basis_xform(diff); } -#endif if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } @@ -1271,12 +1259,10 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); - /* ADD_GROUP("Orbit Velocity", "orbit_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); -*/ ADD_GROUP("Linear Accel", "linear_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); @@ -1332,6 +1318,8 @@ void CPUParticles2D::_bind_methods() { BIND_ENUM_CONSTANT(PARAM_MAX); BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); + BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); // Unused, but exposed for consistency with 3D. + BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); // Unused, but exposed for consistency with 3D. BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); @@ -1370,7 +1358,7 @@ CPUParticles2D::CPUParticles2D() { set_spread(45); set_flatness(0); set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1); - //set_param(PARAM_ORBIT_VELOCITY, 0); + set_param(PARAM_ORBIT_VELOCITY, 0); set_param(PARAM_LINEAR_ACCEL, 0); set_param(PARAM_ANGULAR_VELOCITY, 0); set_param(PARAM_RADIAL_ACCEL, 0); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 81343a4604..79444407ee 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -68,6 +68,8 @@ public: enum Flags { FLAG_ALIGN_Y_TO_VELOCITY, + FLAG_ROTATE_Y, // Unused, but exposed for consistency with 3D. + FLAG_DISABLE_Z, // Unused, but exposed for consistency with 3D. FLAG_MAX }; diff --git a/scene/3d/cpu_particles.cpp b/scene/3d/cpu_particles.cpp index a81071492b..b42649b35a 100644 --- a/scene/3d/cpu_particles.cpp +++ b/scene/3d/cpu_particles.cpp @@ -302,9 +302,9 @@ void CPUParticles::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve) case PARAM_ANGULAR_VELOCITY: { _adjust_curve_range(p_curve, -360, 360); } break; - /*case PARAM_ORBIT_VELOCITY: { + case PARAM_ORBIT_VELOCITY: { _adjust_curve_range(p_curve, -500, 500); - } break;*/ + } break; case PARAM_LINEAR_ACCEL: { _adjust_curve_range(p_curve, -200, 200); } break; @@ -461,11 +461,10 @@ void CPUParticles::_validate_property(PropertyInfo &property) const { if (property.name == "emission_normals" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = 0; } - /* + if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) { property.usage = 0; } - */ } static uint32_t idhash(uint32_t x) { @@ -698,16 +697,14 @@ void CPUParticles::_particles_process(float p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]); } - /* - float tex_orbit_velocity = 0.0; + float tex_orbit_velocity = 0.0; if (flags[FLAG_DISABLE_Z]) { - - if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) { - tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]); + if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(p.custom[1]); } } -*/ + float tex_angular_velocity = 0.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]); @@ -772,18 +769,18 @@ void CPUParticles::_particles_process(float p_delta) { //apply attractor forces p.velocity += force * local_delta; //orbit velocity -#if 0 if (flags[FLAG_DISABLE_Z]) { - - float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random); + float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); if (orbit_amount != 0.0) { - float ang = orbit_amount * DELTA * pi * 2.0; - mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang))); - TRANSFORM[3].xy -= diff.xy; - TRANSFORM[3].xy += rot * diff.xy; + float ang = orbit_amount * local_delta * Math_PI * 2.0; + // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, + // but we use -ang here to reproduce its behavior. + Transform2D rot = Transform2D(-ang, Vector2()); + Vector2 rotv = rot.basis_xform(Vector2(diff.x, diff.y)); + p.transform.origin -= Vector3(diff.x, diff.y, 0); + p.transform.origin += Vector3(rotv.x, rotv.y, 0); } } -#endif if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } @@ -1179,7 +1176,7 @@ void CPUParticles::convert_from_particles(Node *p_particles) { CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); - // CONVERT_PARAM(PARAM_ORBIT_VELOCITY); + CONVERT_PARAM(PARAM_ORBIT_VELOCITY); CONVERT_PARAM(PARAM_LINEAR_ACCEL); CONVERT_PARAM(PARAM_RADIAL_ACCEL); CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL); @@ -1322,12 +1319,10 @@ void CPUParticles::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); - /* ADD_GROUP("Orbit Velocity", "orbit_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); -*/ ADD_GROUP("Linear Accel", "linear_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); @@ -1370,7 +1365,7 @@ void CPUParticles::_bind_methods() { BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY); - //BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); + BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); BIND_ENUM_CONSTANT(PARAM_LINEAR_ACCEL); BIND_ENUM_CONSTANT(PARAM_RADIAL_ACCEL); BIND_ENUM_CONSTANT(PARAM_TANGENTIAL_ACCEL); @@ -1384,6 +1379,7 @@ void CPUParticles::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); + BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); @@ -1422,7 +1418,7 @@ CPUParticles::CPUParticles() { set_spread(45); set_flatness(0); set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1); - //set_param(PARAM_ORBIT_VELOCITY, 0); + set_param(PARAM_ORBIT_VELOCITY, 0); set_param(PARAM_LINEAR_ACCEL, 0); set_param(PARAM_RADIAL_ACCEL, 0); set_param(PARAM_TANGENTIAL_ACCEL, 0); diff --git a/scene/3d/cpu_particles.h b/scene/3d/cpu_particles.h index b863a3cb3f..6a989251f1 100644 --- a/scene/3d/cpu_particles.h +++ b/scene/3d/cpu_particles.h @@ -53,7 +53,7 @@ public: PARAM_INITIAL_LINEAR_VELOCITY, PARAM_ANGULAR_VELOCITY, - //PARAM_ORBIT_VELOCITY, + PARAM_ORBIT_VELOCITY, PARAM_LINEAR_ACCEL, PARAM_RADIAL_ACCEL, PARAM_TANGENTIAL_ACCEL, diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h index 0e7d9be505..3b924e0454 100644 --- a/scene/3d/visual_instance.h +++ b/scene/3d/visual_instance.h @@ -89,6 +89,7 @@ class GeometryInstance : public VisualInstance { public: enum Flags { FLAG_USE_BAKED_LIGHT = VS::INSTANCE_FLAG_USE_BAKED_LIGHT, + FLAG_DRAW_NEXT_FRAME_IF_VISIBLE = VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, FLAG_MAX = VS::INSTANCE_FLAG_MAX, }; diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index e9b38ae990..20a09696e1 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -1049,7 +1049,7 @@ AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node return CONNECTION_ERROR_NO_INPUT; } - if (!nodes.has(p_input_node)) { + if (p_input_node == p_output_node) { return CONNECTION_ERROR_SAME_NODE; } diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 5ef2557383..fadf5e432c 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -216,9 +216,7 @@ void BaseButton::set_pressed(bool p_pressed) { if (p_pressed) { _unpress_group(); } - if (toggle_mode) { - _toggled(status.pressed); - } + _toggled(status.pressed); update(); } @@ -337,9 +335,6 @@ bool BaseButton::is_keep_pressed_outside() const { void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) { - if (shortcut.is_null() == p_shortcut.is_null()) - return; - shortcut = p_shortcut; set_process_unhandled_input(shortcut.is_valid()); } @@ -356,11 +351,10 @@ void BaseButton::_unhandled_input(Ref<InputEvent> p_event) { return; //ignore because of modal window if (is_toggle_mode()) { - set_pressed(!is_pressed()); - emit_signal("toggled", is_pressed()); + set_pressed(!is_pressed()); // Also calls _toggled() internally. } - emit_signal("pressed"); + _pressed(); } } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 76275c2420..9d7c08d3f3 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,23 +29,23 @@ /*************************************************************************/ #include "control.h" -#include "core/project_settings.h" -#include "scene/main/canvas_layer.h" -#include "scene/main/viewport.h" -#include "servers/visual_server.h" #include "core/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/print_string.h" +#include "core/project_settings.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" +#include "scene/main/canvas_layer.h" +#include "scene/main/viewport.h" #include "scene/scene_string_names.h" +#include "servers/visual_server.h" + #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" #endif -#include <stdio.h> Dictionary Control::_edit_get_state() const { diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index cfbc9d6d18..75f5f79873 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -29,10 +29,17 @@ /*************************************************************************/ #include "gradient_edit.h" + #include "core/os/keyboard.h" -#include "editor/editor_scale.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_scale.h" #define SPACING (3 * EDSCALE) +#define POINT_WIDTH (8 * EDSCALE) +#else +#define SPACING 3 +#define POINT_WIDTH 8 +#endif GradientEdit::GradientEdit() { grabbed = -1; diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index 662278a17b..6f31107729 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -36,8 +36,6 @@ #include "scene/resources/default_theme/theme_data.h" #include "scene/resources/gradient.h" -#define POINT_WIDTH (8 * EDSCALE) - class GradientEdit : public Control { GDCLASS(GradientEdit, Control); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 53035fbb9b..6b40ecfc6b 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -6344,14 +6344,14 @@ int TextEdit::get_info_gutter_width() const { return info_gutter_width; } -void TextEdit::set_hiding_enabled(int p_enabled) { +void TextEdit::set_hiding_enabled(bool p_enabled) { if (!p_enabled) unhide_all_lines(); hiding_enabled = p_enabled; update(); } -int TextEdit::is_hiding_enabled() const { +bool TextEdit::is_hiding_enabled() const { return hiding_enabled; } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 68e590f1e6..0c26602d2b 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -697,8 +697,8 @@ public: void set_info_gutter_width(int p_gutter_width); int get_info_gutter_width() const; - void set_hiding_enabled(int p_enabled); - int is_hiding_enabled() const; + void set_hiding_enabled(bool p_enabled); + bool is_hiding_enabled() const; void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 2007ae2669..522c1ecb6a 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -39,7 +39,7 @@ #include "scene/main/viewport.h" #ifdef TOOLS_ENABLED -#include "editor/editor_node.h" +#include "editor/editor_scale.h" #endif #include <limits.h> @@ -318,7 +318,7 @@ void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName void TreeItem::set_collapsed(bool p_collapsed) { - if (collapsed == p_collapsed) + if (collapsed == p_collapsed || !tree) return; collapsed = p_collapsed; TreeItem *ci = tree->selected_item; @@ -344,8 +344,7 @@ void TreeItem::set_collapsed(bool p_collapsed) { } _changed_notify(); - if (tree) - tree->emit_signal("item_collapsed", this); + tree->emit_signal("item_collapsed", this); } bool TreeItem::is_collapsed() { @@ -3721,6 +3720,10 @@ String Tree::get_tooltip(const Point2 &p_pos) const { const TreeItem::Cell &c = it->cells[col]; int col_width = get_column_width(col); + + for (int i = 0; i < col; i++) + pos.x -= get_column_width(i); + for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture> b = c.buttons[j].texture; Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp index 6efd05593e..42b9f19d9d 100644 --- a/servers/visual/shader_language.cpp +++ b/servers/visual/shader_language.cpp @@ -132,6 +132,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "TYPE_SAMPLERCUBE", "INTERPOLATION_FLAT", "INTERPOLATION_SMOOTH", + "CONST", "PRECISION_LOW", "PRECISION_MID", "PRECISION_HIGH", @@ -271,6 +272,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = { { TK_TYPE_SAMPLERCUBE, "samplerCube" }, { TK_INTERPOLATION_FLAT, "flat" }, { TK_INTERPOLATION_SMOOTH, "smooth" }, + { TK_CONST, "const" }, { TK_PRECISION_LOW, "lowp" }, { TK_PRECISION_MID, "mediump" }, { TK_PRECISION_HIGH, "highp" }, @@ -920,6 +922,16 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, const Map<String return true; } + if (shader->constants.has(p_identifier)) { + if (r_data_type) { + *r_data_type = shader->constants[p_identifier].type; + } + if (r_type) { + *r_type = IDENTIFIER_CONSTANT; + } + return true; + } + for (int i = 0; i < shader->functions.size(); i++) { if (!shader->functions[i].callable) @@ -2699,6 +2711,12 @@ bool ShaderLanguage::_validate_assign(Node *p_node, const Map<StringName, BuiltI return false; } + if (shader->constants.has(var->name)) { + if (r_message) + *r_message = RTR("Constants cannot be modified."); + return false; + } + if (!(p_builtin_types.has(var->name) && p_builtin_types[var->name].constant)) { return true; } @@ -3993,7 +4011,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui } else { - //nothng else, so expression + //nothing else, so expression _set_tkpos(pos); //rollback Node *expr = _parse_and_reduce_expression(p_block, p_builtin_types); if (!expr) @@ -4322,24 +4340,30 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct } break; default: { - //function + //function or constant variable + bool is_constant = false; DataPrecision precision = PRECISION_DEFAULT; DataType type; StringName name; + if (tk.type == TK_CONST) { + is_constant = true; + tk = _get_token(); + } + if (is_token_precision(tk.type)) { precision = get_token_precision(tk.type); tk = _get_token(); } if (!is_token_datatype(tk.type)) { - _set_error("Expected function, uniform or varying "); + _set_error("Expected constant, function, uniform or varying "); return ERR_PARSE_ERROR; } if (!is_token_variable_datatype(tk.type)) { - _set_error("Invalid data type for function return (samplers not allowed)"); + _set_error("Invalid data type for constants or function return (samplers not allowed)"); return ERR_PARSE_ERROR; } @@ -4359,8 +4383,73 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct tk = _get_token(); if (tk.type != TK_PARENTHESIS_OPEN) { - _set_error("Expected '(' after identifier"); - return ERR_PARSE_ERROR; + if (type == TYPE_VOID) { + _set_error("Expected '(' after function identifier"); + return ERR_PARSE_ERROR; + } + + //variable + + while (true) { + ShaderNode::Constant constant; + constant.type = type; + constant.precision = precision; + constant.initializer = NULL; + + if (tk.type == TK_OP_ASSIGN) { + + if (!is_constant) { + _set_error("Expected 'const' keyword before constant definition"); + return ERR_PARSE_ERROR; + } + + //variable created with assignment! must parse an expression + Node *expr = _parse_and_reduce_expression(NULL, Map<StringName, BuiltInInfo>()); + if (!expr) + return ERR_PARSE_ERROR; + + if (expr->type != Node::TYPE_CONSTANT) { + _set_error("Expected constant expression after '='"); + return ERR_PARSE_ERROR; + } + + constant.initializer = static_cast<ConstantNode *>(expr); + + if (type != expr->get_datatype()) { + _set_error("Invalid assignment of '" + get_datatype_name(expr->get_datatype()) + "' to '" + get_datatype_name(type) + "'"); + return ERR_PARSE_ERROR; + } + tk = _get_token(); + } else { + _set_error("Expected initialization of constant"); + return ERR_PARSE_ERROR; + } + + shader->constants[name] = constant; + if (tk.type == TK_COMMA) { + tk = _get_token(); + if (tk.type != TK_IDENTIFIER) { + _set_error("Expected identifier after type"); + return ERR_PARSE_ERROR; + } + + name = tk.text; + if (_find_identifier(NULL, Map<StringName, BuiltInInfo>(), name)) { + _set_error("Redefinition of '" + String(name) + "'"); + return ERR_PARSE_ERROR; + } + + tk = _get_token(); + + } else if (tk.type == TK_SEMICOLON) { + break; + } else { + _set_error("Expected ',' or ';' after constant"); + return ERR_PARSE_ERROR; + } + } + + break; } Map<StringName, BuiltInInfo> builtin_types; diff --git a/servers/visual/shader_language.h b/servers/visual/shader_language.h index 67c273d267..934dc2c403 100644 --- a/servers/visual/shader_language.h +++ b/servers/visual/shader_language.h @@ -80,6 +80,7 @@ public: TK_TYPE_SAMPLERCUBE, TK_INTERPOLATION_FLAT, TK_INTERPOLATION_SMOOTH, + TK_CONST, TK_PRECISION_LOW, TK_PRECISION_MID, TK_PRECISION_HIGH, @@ -440,6 +441,13 @@ public: }; struct ShaderNode : public Node { + + struct Constant { + DataType type; + DataPrecision precision; + ConstantNode *initializer; + }; + struct Function { StringName name; FunctionNode *function; @@ -492,6 +500,7 @@ public: } }; + Map<StringName, Constant> constants; Map<StringName, Varying> varyings; Map<StringName, Uniform> uniforms; Vector<StringName> render_modes; @@ -632,6 +641,7 @@ private: IDENTIFIER_FUNCTION_ARGUMENT, IDENTIFIER_LOCAL_VAR, IDENTIFIER_BUILTIN_VAR, + IDENTIFIER_CONSTANT, }; bool _find_identifier(const BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, const StringName &p_identifier, DataType *r_data_type = NULL, IdentifierType *r_type = NULL); |