diff options
58 files changed, 519 insertions, 97 deletions
diff --git a/core/input/input.compat.inc b/core/input/input.compat.inc new file mode 100644 index 0000000000..cbc8b1df0f --- /dev/null +++ b/core/input/input.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* input.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 DISABLE_DEPRECATED + +void Input::_vibrate_handheld_bind_compat_91143(int p_duration_ms) { + vibrate_handheld(p_duration_ms, -1.0); +} + +void Input::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::_vibrate_handheld_bind_compat_91143, DEFVAL(500)); +} + +#endif // DISABLE_DEPRECATED diff --git a/core/input/input.cpp b/core/input/input.cpp index c24a59203f..1eabfacd8e 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "input.h" +#include "input.compat.inc" #include "core/config/project_settings.h" #include "core/input/default_controller_mappings.h" @@ -120,7 +121,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("get_joy_vibration_duration", "device"), &Input::get_joy_vibration_duration); ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0)); ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration); - ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::vibrate_handheld, DEFVAL(500)); + ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0)); ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity); ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer); ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer); @@ -803,8 +804,8 @@ void Input::stop_joy_vibration(int p_device) { joy_vibration[p_device] = vibration; } -void Input::vibrate_handheld(int p_duration_ms) { - OS::get_singleton()->vibrate_handheld(p_duration_ms); +void Input::vibrate_handheld(int p_duration_ms, float p_amplitude) { + OS::get_singleton()->vibrate_handheld(p_duration_ms, p_amplitude); } void Input::set_gravity(const Vector3 &p_gravity) { diff --git a/core/input/input.h b/core/input/input.h index d1f284e8f7..93407da2d9 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -264,6 +264,11 @@ private: EventDispatchFunc event_dispatch_function = nullptr; +#ifndef DISABLE_DEPRECATED + void _vibrate_handheld_bind_compat_91143(int p_duration_ms = 500); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + protected: static void _bind_methods(); @@ -323,7 +328,7 @@ public: void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0); void stop_joy_vibration(int p_device); - void vibrate_handheld(int p_duration_ms = 500); + void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0); void set_mouse_position(const Point2 &p_posf); diff --git a/core/os/os.h b/core/os/os.h index d20f84b4ff..63cc6ed50e 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -185,7 +185,7 @@ public: virtual int get_process_id() const; virtual bool is_process_running(const ProcessID &p_pid) const = 0; virtual int get_process_exit_code(const ProcessID &p_pid) const = 0; - virtual void vibrate_handheld(int p_duration_ms = 500) {} + virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) {} virtual Error shell_open(const String &p_uri); virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder = true); diff --git a/doc/classes/AudioEffectSpectrumAnalyzer.xml b/doc/classes/AudioEffectSpectrumAnalyzer.xml index d9312cc87d..fbc0c2275f 100644 --- a/doc/classes/AudioEffectSpectrumAnalyzer.xml +++ b/doc/classes/AudioEffectSpectrumAnalyzer.xml @@ -9,7 +9,6 @@ </description> <tutorials> <link title="Audio Spectrum Visualizer Demo">https://godotengine.org/asset-library/asset/2762</link> - <link title="Godot 3.2 will get new audio features">https://godotengine.org/article/godot-32-will-get-new-audio-features</link> </tutorials> <members> <member name="buffer_length" type="float" setter="set_buffer_length" getter="get_buffer_length" default="2.0"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index fe67c2a38e..a054048266 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1167,12 +1167,21 @@ [b]Note:[/b] This method is implemented on Android, iOS, macOS, Windows, and Linux (X11/Wayland). </description> </method> + <method name="status_indicator_get_rect" qualifiers="const"> + <return type="Rect2" /> + <param index="0" name="id" type="int" /> + <description> + Returns the rectangle for the given status indicator [param id] in screen coordinates. If the status indicator is not visible, returns an empty [Rect2]. + [b]Note:[/b] This method is implemented on macOS and Windows. + </description> + </method> <method name="status_indicator_set_callback"> <return type="void" /> <param index="0" name="id" type="int" /> <param index="1" name="callback" type="Callable" /> <description> Sets the application status indicator activation callback. + [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> <method name="status_indicator_set_icon"> @@ -1181,6 +1190,7 @@ <param index="1" name="icon" type="Texture2D" /> <description> Sets the application status indicator icon. + [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> <method name="status_indicator_set_menu"> @@ -1200,6 +1210,7 @@ <param index="1" name="tooltip" type="String" /> <description> Sets the application status indicator tooltip. + [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> <method name="tablet_get_current_driver" qualifiers="const"> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 93a7b09fce..0c08dd3605 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -881,7 +881,7 @@ All update modes will ignore builds with different major versions (e.g. Godot 4 -> Godot 5). </member> <member name="network/connection/network_mode" type="int" setter="" getter=""> - Determines whether online features are enabled in the editor, such as the Asset Library or update checks. Disabling these online features helps alleviate privacy concerns by preventing the editor from making HTTP requests to the Godot website, GitHub, or third-party platforms hosting assets from the Asset Library. + Determines whether online features are enabled in the editor, such as the Asset Library or update checks. Disabling these online features helps alleviate privacy concerns by preventing the editor from making HTTP requests to the Godot website or third-party platforms hosting assets from the Asset Library. </member> <member name="network/debug/remote_host" type="String" setter="" getter=""> The address to listen to when starting the remote debugger. This can be set to [code]0.0.0.0[/code] to allow external clients to connect to the remote debugger (instead of restricting the remote debugger to connections from [code]localhost[/code]). diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 119ecb7f0e..642bb76e75 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -397,11 +397,14 @@ <method name="vibrate_handheld"> <return type="void" /> <param index="0" name="duration_ms" type="int" default="500" /> + <param index="1" name="amplitude" type="float" default="-1.0" /> <description> + [b]Note:[/b] While [code skip-lint]amplitude[/code] expects a value between 0 and 1, -1 does the default amplitude for the device. Vibrate the handheld device for the specified duration in milliseconds. [b]Note:[/b] This method is implemented on Android, iOS, and Web. It has no effect on other platforms. [b]Note:[/b] For Android, [method vibrate_handheld] requires enabling the [code]VIBRATE[/code] permission in the export preset. Otherwise, [method vibrate_handheld] will have no effect. [b]Note:[/b] For iOS, specifying the duration is only supported in iOS 13 and later. + [b]Note:[/b] For Web, the amplitude cannot be changed. [b]Note:[/b] Some web browsers such as Safari and Firefox for Android do not support [method vibrate_handheld]. </description> </method> diff --git a/doc/classes/StatusIndicator.xml b/doc/classes/StatusIndicator.xml index fb156b3c9f..688840b17f 100644 --- a/doc/classes/StatusIndicator.xml +++ b/doc/classes/StatusIndicator.xml @@ -8,6 +8,14 @@ </description> <tutorials> </tutorials> + <methods> + <method name="get_rect" qualifiers="const"> + <return type="Rect2" /> + <description> + Returns the status indicator rectangle in screen coordinates. If this status indicator is not visible, returns an empty [Rect2]. + </description> + </method> + </methods> <members> <member name="icon" type="Texture2D" setter="set_icon" getter="get_icon"> Status indicator icon. diff --git a/editor/engine_update_label.cpp b/editor/engine_update_label.cpp index 1d7df806de..9984d6f02f 100644 --- a/editor/engine_update_label.cpp +++ b/editor/engine_update_label.cpp @@ -30,6 +30,7 @@ #include "engine_update_label.h" +#include "core/io/json.h" #include "core/os/time.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -46,7 +47,7 @@ bool EngineUpdateLabel::_can_check_updates() const { void EngineUpdateLabel::_check_update() { checked_update = true; _set_status(UpdateStatus::BUSY); - http->request("https://raw.githubusercontent.com/godotengine/godot-website/master/_data/versions.yml"); + http->request("https://godotengine.org/versions.json"); } void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_code, const PackedStringArray &p_headers, const PackedByteArray &p_body) { @@ -62,12 +63,24 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod return; } - PackedStringArray lines; + Array version_data; { String s; const uint8_t *r = p_body.ptr(); s.parse_utf8((const char *)r, p_body.size()); - lines = s.split("\n"); + + Variant result = JSON::parse_string(s); + if (result == Variant()) { + _set_status(UpdateStatus::ERROR); + _set_message(TTR("Failed to parse version JSON."), theme_cache.error_color); + return; + } + if (result.get_type() != Variant::ARRAY) { + _set_status(UpdateStatus::ERROR); + _set_message(TTR("Received JSON data is not a valid version array."), theme_cache.error_color); + return; + } + version_data = result; } UpdateMode update_mode = UpdateMode(int(EDITOR_GET("network/connection/engine_version_update_mode"))); @@ -78,14 +91,11 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod int current_minor = version_info["minor"]; int current_patch = version_info["patch"]; - int current_version_line = -1; - for (int i = 0; i < lines.size(); i++) { - const String &line = lines[i]; - if (!line.begins_with("- name")) { - continue; - } + Dictionary found_version_info; + for (const Variant &data_bit : version_data) { + const Dictionary info = data_bit; - const String version_string = _extract_sub_string(line); + const String version_string = info["name"]; const PackedStringArray version_bits = version_string.split("."); if (version_bits.size() < 2) { @@ -111,7 +121,7 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod } if (minor > current_minor || patch > current_patch) { - String version_type = _extract_sub_string(lines[i + 1]); + String version_type = info["flavor"]; if (stable_only && _get_version_type(version_type, nullptr) != VersionType::STABLE) { continue; } @@ -120,17 +130,17 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod found_version += "-" + version_type; break; } else if (minor == current_minor && patch == current_patch) { - current_version_line = i; + found_version_info = info; found_version = version_string; break; } } - if (current_version_line == -1 && !found_version.is_empty()) { + if (found_version_info.is_empty() && !found_version.is_empty()) { _set_status(UpdateStatus::UPDATE_AVAILABLE); _set_message(vformat(TTR("Update available: %s."), found_version), theme_cache.update_color); return; - } else if (current_version_line == -1 || stable_only) { + } else if (found_version_info.is_empty() || stable_only) { _set_status(UpdateStatus::UP_TO_DATE); return; } @@ -138,17 +148,11 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod int current_version_index; VersionType current_version_type = _get_version_type(version_info["status"], ¤t_version_index); - for (int i = current_version_line + 1; i < lines.size(); i++) { - const String &line = lines[i]; - if (line.begins_with("- name")) { - break; - } - - if (!line.begins_with(" - name") && !line.begins_with(" flavor")) { - continue; - } + const Array releases = found_version_info["releases"]; + for (const Variant &data_bit : version_data) { + const Dictionary info = data_bit; - const String version_string = _extract_sub_string(line); + const String version_string = info["name"]; int version_index; VersionType version_type = _get_version_type(version_string, &version_index); diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index c0a704105c..814484b885 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -59,17 +59,16 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - if (updown_offset != -1 && mb->get_position().x > updown_offset) { - //there is an updown, so use it. + if (updown_offset != -1 && ((!is_layout_rtl() && mb->get_position().x > updown_offset) || (is_layout_rtl() && mb->get_position().x < updown_offset))) { + // Updown pressed. if (mb->get_position().y < get_size().height / 2) { set_value(get_value() + get_step()); } else { set_value(get_value() - get_step()); } return; - } else { - _grab_start(); } + _grab_start(); } else { _grab_end(); } @@ -121,7 +120,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { } } } else if (updown_offset != -1) { - bool new_hover = (mm->get_position().x > updown_offset); + bool new_hover = (!is_layout_rtl() && mm->get_position().x > updown_offset) || (is_layout_rtl() && mm->get_position().x < updown_offset); if (new_hover != hover_updown) { hover_updown = new_hover; queue_redraw(); @@ -296,11 +295,9 @@ void EditorSpinSlider::_update_value_input_stylebox() { // higher margin to match the location where the text begins. // The margin values below were determined by empirical testing. if (is_layout_rtl()) { - stylebox->set_content_margin(SIDE_LEFT, 0); stylebox->set_content_margin(SIDE_RIGHT, (!get_label().is_empty() ? 23 : 16) * EDSCALE); } else { stylebox->set_content_margin(SIDE_LEFT, (!get_label().is_empty() ? 23 : 16) * EDSCALE); - stylebox->set_content_margin(SIDE_RIGHT, 0); } value_input->add_theme_style_override("normal", stylebox); @@ -394,6 +391,9 @@ void EditorSpinSlider::_draw_spin_slider() { c *= Color(1.2, 1.2, 1.2); } draw_texture(updown2, Vector2(updown_offset, updown_vofs), c); + if (rtl) { + updown_offset += updown2->get_width(); + } if (grabber->is_visible()) { grabber->hide(); } @@ -705,6 +705,7 @@ void EditorSpinSlider::_ensure_input_popup() { } value_input_popup = memnew(Control); + value_input_popup->set_anchors_and_offsets_preset(PRESET_FULL_RECT); add_child(value_input_popup); value_input = memnew(LineEdit); diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 5a9976dc71..55a4e0b18c 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -337,3 +337,10 @@ GH-91098 Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/remove_paragraph/arguments': size changed value in new API, from 1 to 2. Added optional argument. Compatibility method registered. + + +GH-91143 +-------- +Validate extension JSON: Error: Field 'classes/Input/methods/vibrate_handheld/arguments': size changed value in new API, from 1 to 2. + +Added optional argument. Compatibility method registered.
\ No newline at end of file diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index f238958f25..73abf71bde 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1958,19 +1958,22 @@ int GDScriptInstance::get_method_argument_count(const StringName &p_method, bool return 0; } +void GDScriptInstance::_call_implicit_ready_recursively(GDScript *p_script) { + // Call base class first. + if (p_script->_base) { + _call_implicit_ready_recursively(p_script->_base); + } + if (p_script->implicit_ready) { + Callable::CallError err; + p_script->implicit_ready->call(this, nullptr, 0, err); + } +} + Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *sptr = script.ptr(); if (unlikely(p_method == SNAME("_ready"))) { - // Call implicit ready first, including for the super classes. - while (sptr) { - if (sptr->implicit_ready) { - sptr->implicit_ready->call(this, nullptr, 0, r_error); - } - sptr = sptr->_base; - } - - // Reset this back for the regular call. - sptr = script.ptr(); + // Call implicit ready first, including for the super classes recursively. + _call_implicit_ready_recursively(sptr); } while (sptr) { HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 7bd68ac0b1..51267ecb84 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -365,6 +365,8 @@ class GDScriptInstance : public ScriptInstance { SelfList<GDScriptFunctionState>::List pending_func_states; + void _call_implicit_ready_recursively(GDScript *p_script); + public: virtual Object *get_owner() { return owner; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 28a44357eb..ec20811385 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3384,7 +3384,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (parent_function) { - push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); + push_error(vformat(R"*(Cannot call non-static function "%s()" from the static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); } else { push_error(vformat(R"*(Cannot call non-static function "%s()" from a static variable initializer.)*", p_call->function_name), p_call); } @@ -3801,6 +3801,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (is_base && (!base.is_meta_type || member.function->is_static || is_constructor)) { p_identifier->set_datatype(make_callable_type(member.function->info)); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; + p_identifier->function_source = member.function; + p_identifier->function_source_is_static = member.function->is_static; return; } } break; @@ -3849,6 +3851,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (method_info.name == p_identifier->name) { p_identifier->set_datatype(make_callable_type(method_info)); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; + p_identifier->function_source_is_static = method_info.flags & METHOD_FLAG_STATIC; return; } @@ -4029,25 +4032,37 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } if (found_source) { - bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; - bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; - if ((source_is_variable || source_is_signal) && static_context) { + const bool source_is_instance_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; + const bool source_is_instance_function = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_FUNCTION && !p_identifier->function_source_is_static; + const bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + + if (static_context && (source_is_instance_variable || source_is_instance_function || source_is_signal)) { // Get the parent function above any lambda. GDScriptParser::FunctionNode *parent_function = parser->current_function; while (parent_function && parent_function->source_lambda) { parent_function = parent_function->source_lambda->parent_function; } + String source_type; + if (source_is_instance_variable) { + source_type = "non-static variable"; + } else if (source_is_instance_function) { + source_type = "non-static function"; + } else { // source_is_signal + source_type = "signal"; + } + if (parent_function) { - push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_type, p_identifier->name, parent_function->identifier->name), p_identifier); } else { - push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_type, p_identifier->name), p_identifier); } } if (current_lambda != nullptr) { - // If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance. - if (source_is_variable || source_is_signal) { + // If the identifier is a member variable (including the native class properties), member function, or a signal, + // we consider the lambda to be using `self`, so we keep a reference to the current instance. + if (source_is_instance_variable || source_is_instance_function || source_is_signal) { mark_lambda_use_self(); return; // No need to capture. } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7fb9ffe9a5..1e67e2d496 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -902,8 +902,11 @@ public: VariableNode *variable_source; ConstantNode *constant_source; SignalNode *signal_source; + FunctionNode *function_source; }; - FunctionNode *source_function = nullptr; + bool function_source_is_static = false; // For non-GDScript scripts. + + FunctionNode *source_function = nullptr; // TODO: Rename to disambiguate `function_source`. int usages = 0; // Useful for binds/iterator variable. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static.gd new file mode 100644 index 0000000000..e041aeb914 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static.gd @@ -0,0 +1,10 @@ +# GH-91403 + +static func static_func(): + print(non_static_func) + +func non_static_func(): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static.out new file mode 100644 index 0000000000..d8d6c8bc1b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot access non-static function "non_static_func" from the static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static_in_lambda_param.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static_in_lambda_param.gd new file mode 100644 index 0000000000..36bc9dbf15 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static_in_lambda_param.gd @@ -0,0 +1,15 @@ +# GH-91403 + +func non_static_func(): + pass + +static func static_func( + f := func (): + var g := func (): + print(non_static_func) + g.call() +): + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static_in_lambda_param.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static_in_lambda_param.out new file mode 100644 index 0000000000..d8d6c8bc1b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_access_non_static_in_lambda_param.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot access non-static function "non_static_func" from the static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out index b78f131345..c094c08cd8 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot call non-static function "non_static_func()" from static function "static_func()". +Cannot call non-static function "non_static_func()" from the static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out index b78f131345..c094c08cd8 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot call non-static function "non_static_func()" from static function "static_func()". +Cannot call non-static function "non_static_func()" from the static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out index b78f131345..c094c08cd8 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot call non-static function "non_static_func()" from static function "static_func()". +Cannot call non-static function "non_static_func()" from the static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda.gd new file mode 100644 index 0000000000..7ae5bea7d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda.gd @@ -0,0 +1,14 @@ +# GH-91403 + +func non_static_func(): + pass + +static var static_var = func (): + var f := func (): + var g := func (): + print(non_static_func) + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda.out new file mode 100644 index 0000000000..153e81b405 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot access non-static function "non_static_func" from a static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda_setter.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda_setter.gd new file mode 100644 index 0000000000..7479afc532 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda_setter.gd @@ -0,0 +1,15 @@ +# GH-91403 + +func non_static_func(): + pass + +static var static_var: + set(_value): + var f := func (): + var g := func (): + print(non_static_func) + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda_setter.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda_setter.out new file mode 100644 index 0000000000..de43f2d3c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_access_non_static_in_lambda_setter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot access non-static function "non_static_func" from the static function "@static_var_setter()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out index cdf3ab2aeb..a285b80025 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot call non-static function "non_static_func()" from static function "@static_var_setter()". +Cannot call non-static function "non_static_func()" from the static function "@static_var_setter()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_access.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_access.gd new file mode 100644 index 0000000000..a21b2c4470 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_access.gd @@ -0,0 +1,11 @@ +# GH-91403 + +@static_unload + +func non_static(): + return "non static" + +static var static_var = Callable(non_static) + +func test(): + print("does not run") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_access.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_access.out new file mode 100644 index 0000000000..a95069dc4f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_access.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot access non-static function "non_static" from a static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_non_static_access.gd b/modules/gdscript/tests/scripts/analyzer/features/static_non_static_access.gd new file mode 100644 index 0000000000..80ceb6d1a9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/static_non_static_access.gd @@ -0,0 +1,75 @@ +@static_unload + +static var static_var +var non_static_var + +signal my_signal() + +static func static_func(): + pass + +func non_static_func(): + pass + +static var test_static_var_lambda = func (): + static_func() + print(static_func) + static_var = 1 + print(static_var) + +var test_non_static_var_lambda = func (): + static_func() + print(static_func) + static_var = 1 + print(static_var) + + non_static_func() + print(non_static_func) + non_static_var = 1 + print(non_static_var) + my_signal.emit() + print(my_signal) + +static var test_static_var_setter: + set(_value): + static_func() + print(static_func) + static_var = 1 + print(static_var) + +var test_non_static_var_setter: + set(_value): + static_func() + print(static_func) + static_var = 1 + print(static_var) + + non_static_func() + print(non_static_func) + non_static_var = 1 + print(non_static_var) + my_signal.emit() + print(my_signal) + +static func test_static_func(): + static_func() + print(static_func) + static_var = 1 + print(static_var) + +func test_non_static_func(): + static_func() + print(static_func) + static_var = 1 + print(static_var) + + non_static_func() + print(non_static_func) + non_static_var = 1 + print(non_static_var) + my_signal.emit() + print(my_signal) + +func test(): + test_static_var_lambda = null + test_non_static_var_lambda = null diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_non_static_access.out b/modules/gdscript/tests/scripts/analyzer/features/static_non_static_access.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/static_non_static_access.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd new file mode 100644 index 0000000000..99156adb28 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd @@ -0,0 +1,18 @@ +#GH-63329 +class A extends Node: + @onready var a := get_value("a") + + func get_value(var_name: String) -> String: + print(var_name) + return var_name + +class B extends A: + @onready var b := get_value("b") + + func _ready(): + pass + +func test(): + var node := B.new() + node._ready() + node.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.out b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.out new file mode 100644 index 0000000000..b417ce67ca --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.out @@ -0,0 +1,3 @@ +GDTEST_OK +a +b diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml index 1be8cc828d..8338054142 100644 --- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml +++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml @@ -34,6 +34,9 @@ <member name="k2" type="float" setter="set_k2" getter="get_k2" default="0.215"> The k2 lens factor, see k1. </member> + <member name="offset_rect" type="Rect2" setter="set_offset_rect" getter="get_offset_rect" default="Rect2(0, 0, 1, 1)"> + Set the offset rect relative to the area being rendered. A length of 1 represents the whole rendering area on that axis. + </member> <member name="oversample" type="float" setter="set_oversample" getter="get_oversample" default="1.5"> The oversample setting. Because of the lens distortion we have to render our buffers at a higher resolution then the screen can natively handle. A value between 1.5 and 2.0 often provides good results but at the cost of performance. </member> diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index bba56f6468..d23edcd1d1 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -230,6 +230,9 @@ void MobileVRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_display_to_lens", "display_to_lens"), &MobileVRInterface::set_display_to_lens); ClassDB::bind_method(D_METHOD("get_display_to_lens"), &MobileVRInterface::get_display_to_lens); + ClassDB::bind_method(D_METHOD("set_offset_rect", "offset_rect"), &MobileVRInterface::set_offset_rect); + ClassDB::bind_method(D_METHOD("get_offset_rect"), &MobileVRInterface::get_offset_rect); + ClassDB::bind_method(D_METHOD("set_oversample", "oversample"), &MobileVRInterface::set_oversample); ClassDB::bind_method(D_METHOD("get_oversample"), &MobileVRInterface::get_oversample); @@ -243,6 +246,7 @@ void MobileVRInterface::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "iod", PROPERTY_HINT_RANGE, "4.0,10.0,0.1"), "set_iod", "get_iod"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_width", PROPERTY_HINT_RANGE, "5.0,25.0,0.1"), "set_display_width", "get_display_width"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_to_lens", PROPERTY_HINT_RANGE, "5.0,25.0,0.1"), "set_display_to_lens", "get_display_to_lens"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "offset_rect"), "set_offset_rect", "get_offset_rect"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversample", PROPERTY_HINT_RANGE, "1.0,2.0,0.1"), "set_oversample", "get_oversample"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "k1", PROPERTY_HINT_RANGE, "0.1,10.0,0.0001"), "set_k1", "get_k1"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "k2", PROPERTY_HINT_RANGE, "0.1,10.0,0.0001"), "set_k2", "get_k2"); @@ -256,6 +260,14 @@ double MobileVRInterface::get_eye_height() const { return eye_height; } +void MobileVRInterface::set_offset_rect(const Rect2 &p_offset_rect) { + offset_rect = p_offset_rect; +} + +Rect2 MobileVRInterface::get_offset_rect() const { + return offset_rect; +} + void MobileVRInterface::set_iod(const double p_iod) { intraocular_dist = p_iod; }; @@ -483,6 +495,8 @@ Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, // Because we are rendering to our device we must use our main viewport! ERR_FAIL_COND_V(p_screen_rect == Rect2(), blit_to_screen); + Rect2 modified_screen_rect = Rect2(p_screen_rect.position + offset_rect.position * p_screen_rect.size, p_screen_rect.size * offset_rect.size); + // and add our blits BlitToScreen blit; blit.render_target = p_render_target; @@ -494,16 +508,16 @@ Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, blit.lens_distortion.aspect_ratio = aspect; // left eye - blit.dst_rect = p_screen_rect; + blit.dst_rect = modified_screen_rect; blit.dst_rect.size.width *= 0.5; blit.multi_view.layer = 0; blit.lens_distortion.eye_center.x = ((-intraocular_dist / 2.0) + (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); // right eye - blit.dst_rect = p_screen_rect; + blit.dst_rect = modified_screen_rect; blit.dst_rect.size.width *= 0.5; - blit.dst_rect.position.x = blit.dst_rect.size.width; + blit.dst_rect.position.x += blit.dst_rect.size.width; blit.multi_view.layer = 1; blit.lens_distortion.eye_center.x = ((intraocular_dist / 2.0) - (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); diff --git a/modules/mobile_vr/mobile_vr_interface.h b/modules/mobile_vr/mobile_vr_interface.h index f680d8aa11..e1d43fff74 100644 --- a/modules/mobile_vr/mobile_vr_interface.h +++ b/modules/mobile_vr/mobile_vr_interface.h @@ -62,6 +62,8 @@ private: double display_to_lens = 4.0; double oversample = 1.5; + Rect2 offset_rect = Rect2(0, 0, 1, 1); // Full screen rect. + double k1 = 0.215; double k2 = 0.215; double aspect = 1.0; @@ -121,6 +123,9 @@ public: void set_display_width(const double p_display_width); double get_display_width() const; + void set_offset_rect(const Rect2 &p_offset_rect); + Rect2 get_offset_rect() const; + void set_display_to_lens(const double p_display_to_lens); double get_display_to_lens() const; diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index ce53aeebcb..fbdf07e6c2 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -894,16 +894,25 @@ class Godot(private val context: Context) : SensorEventListener { */ @SuppressLint("MissingPermission") @Keep - private fun vibrate(durationMs: Int) { + private fun vibrate(durationMs: Int, amplitude: Int) { if (durationMs > 0 && requestPermission("VIBRATE")) { val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - vibratorService.vibrate( - VibrationEffect.createOneShot( - durationMs.toLong(), - VibrationEffect.DEFAULT_AMPLITUDE + if (amplitude <= -1) { + vibratorService.vibrate( + VibrationEffect.createOneShot( + durationMs.toLong(), + VibrationEffect.DEFAULT_AMPLITUDE + ) ) - ) + } else { + vibratorService.vibrate( + VibrationEffect.createOneShot( + durationMs.toLong(), + amplitude + ) + ) + } } else { // deprecated in API 26 vibratorService.vibrate(durationMs.toLong()) diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 61be6fc5db..0e766e7d56 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -72,7 +72,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;"); _get_ca_certificates = p_env->GetMethodID(godot_class, "getCACertificates", "()Ljava/lang/String;"); _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V"); - _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); + _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(II)V"); _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); @@ -331,11 +331,18 @@ void GodotJavaWrapper::init_input_devices() { } } -void GodotJavaWrapper::vibrate(int p_duration_ms) { +void GodotJavaWrapper::vibrate(int p_duration_ms, float p_amplitude) { if (_vibrate) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL(env); - env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); + + int j_amplitude = -1.0; + + if (p_amplitude != -1.0) { + j_amplitude = CLAMP(int(p_amplitude * 255), 1, 255); + } + + env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms, j_amplitude); } } diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 93998021a9..e86391d4e3 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -102,7 +102,7 @@ public: Vector<String> get_granted_permissions() const; String get_ca_certificates() const; void init_input_devices(); - void vibrate(int p_duration_ms); + void vibrate(int p_duration_ms, float p_amplitude = -1.0); String get_input_fallback_mapping(); int create_new_godot_instance(const List<String> &args); void begin_benchmark_measure(const String &p_context, const String &p_label); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 463a307854..c60125c34e 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -746,8 +746,8 @@ ANativeWindow *OS_Android::get_native_window() const { #endif } -void OS_Android::vibrate_handheld(int p_duration_ms) { - godot_java->vibrate(p_duration_ms); +void OS_Android::vibrate_handheld(int p_duration_ms, float p_amplitude) { + godot_java->vibrate(p_duration_ms, p_amplitude); } String OS_Android::get_config_path() const { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 7bdbeef77a..b150ef4f61 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -153,7 +153,7 @@ public: virtual Error move_to_trash(const String &p_path) override; - void vibrate_handheld(int p_duration_ms) override; + void vibrate_handheld(int p_duration_ms, float p_amplitude = -1.0) override; virtual String get_config_path() const override; diff --git a/platform/ios/ios.h b/platform/ios/ios.h index d488cde257..cb5be64cee 100644 --- a/platform/ios/ios.h +++ b/platform/ios/ios.h @@ -51,7 +51,7 @@ public: static void alert(const char *p_alert, const char *p_title); bool supports_haptic_engine(); - void vibrate_haptic_engine(float p_duration_seconds); + void vibrate_haptic_engine(float p_duration_seconds, float p_amplitude); String get_model() const; String get_rate_url(int p_app_id) const; diff --git a/platform/ios/ios.mm b/platform/ios/ios.mm index 0a2e1fd5cd..6943de5ac8 100644 --- a/platform/ios/ios.mm +++ b/platform/ios/ios.mm @@ -69,21 +69,41 @@ CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) { return haptic_engine; } -void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) { +void iOS::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API_AVAILABLE(ios(13)) { if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy... if (supports_haptic_engine()) { CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance(); if (cur_haptic_engine) { - NSDictionary *hapticDict = @{ - CHHapticPatternKeyPattern : @[ - @{CHHapticPatternKeyEvent : @{ - CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, - CHHapticPatternKeyTime : @(CHHapticTimeImmediate), - CHHapticPatternKeyEventDuration : @(p_duration_seconds) - }, - }, - ], - }; + NSDictionary *hapticDict; + if (p_amplitude < 0) { + hapticDict = @{ + CHHapticPatternKeyPattern : @[ + @{CHHapticPatternKeyEvent : @{ + CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, + CHHapticPatternKeyTime : @(CHHapticTimeImmediate), + CHHapticPatternKeyEventDuration : @(p_duration_seconds), + }, + }, + ], + }; + } else { + hapticDict = @{ + CHHapticPatternKeyPattern : @[ + @{CHHapticPatternKeyEvent : @{ + CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, + CHHapticPatternKeyTime : @(CHHapticTimeImmediate), + CHHapticPatternKeyEventDuration : @(p_duration_seconds), + CHHapticPatternKeyEventParameters : @[ + @{ + CHHapticPatternKeyParameterID : @("HapticIntensity"), + CHHapticPatternKeyParameterValue : @(p_amplitude) + }, + ], + }, + }, + ], + }; + } NSError *error; CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error]; diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index c4782a4768..b7c5a73065 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -123,7 +123,7 @@ public: virtual String get_unique_id() const override; virtual String get_processor_name() const override; - virtual void vibrate_handheld(int p_duration_ms = 500) override; + virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) override; virtual bool _check_internal_feature_support(const String &p_feature) override; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 52d496d641..35b87ea647 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -571,9 +571,13 @@ String OS_IOS::get_system_font_path(const String &p_font_name, int p_weight, int return ret; } -void OS_IOS::vibrate_handheld(int p_duration_ms) { +void OS_IOS::vibrate_handheld(int p_duration_ms, float p_amplitude) { if (ios->supports_haptic_engine()) { - ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f); + if (p_amplitude > 0.0) { + p_amplitude = CLAMP(p_amplitude, 0.0, 1.0); + } + + ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude); } else { // iOS <13 does not support duration for vibration AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 083e9731c9..db76b7d78a 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -436,6 +436,7 @@ public: virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override; virtual void status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid) override; virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override; + virtual Rect2 status_indicator_get_rect(IndicatorID p_id) const override; virtual void delete_status_indicator(IndicatorID p_id) override; static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index cfa4041147..0041848c78 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3283,6 +3283,30 @@ void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const C [indicators[p_id].delegate setCallback:p_callback]; } +Rect2 DisplayServerMacOS::status_indicator_get_rect(IndicatorID p_id) const { + ERR_FAIL_COND_V(!indicators.has(p_id), Rect2()); + + NSStatusItem *item = indicators[p_id].item; + NSView *v = item.button; + const NSRect contentRect = [v frame]; + const NSRect nsrect = [v.window convertRectToScreen:contentRect]; + Rect2 rect; + + // Return the position of the top-left corner, for macOS the y starts at the bottom. + const float scale = screen_get_max_scale(); + rect.size.x = nsrect.size.width; + rect.size.y = nsrect.size.height; + rect.size *= scale; + rect.position.x = nsrect.origin.x; + rect.position.y = (nsrect.origin.y + nsrect.size.height); + rect.position *= scale; + rect.position -= _get_screens_origin(); + // macOS native y-coordinate relative to _get_screens_origin() is negative, + // Godot expects a positive value. + rect.position.y *= -1; + return rect; +} + void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) { ERR_FAIL_COND(!indicators.has(p_id)); diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index ab4e7f8470..6b6c9ddd63 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -174,7 +174,7 @@ void OS_Web::add_frame_delay(bool p_can_draw) { #endif } -void OS_Web::vibrate_handheld(int p_duration_ms) { +void OS_Web::vibrate_handheld(int p_duration_ms, float p_amplitude) { godot_js_input_vibrate_handheld(p_duration_ms); } diff --git a/platform/web/os_web.h b/platform/web/os_web.h index a825938e96..55a5fcc6c6 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -98,7 +98,7 @@ public: // Implemented in web_main.cpp loop callback instead. void add_frame_delay(bool p_can_draw) override; - void vibrate_handheld(int p_duration_ms) override; + void vibrate_handheld(int p_duration_ms, float p_amplitude) override; String get_cache_path() const override; String get_config_path() const override; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index dbba9b4308..f101d85d58 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3325,6 +3325,30 @@ void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const indicators[p_id].callback = p_callback; } +Rect2 DisplayServerWindows::status_indicator_get_rect(IndicatorID p_id) const { + ERR_FAIL_COND_V(!indicators.has(p_id), Rect2()); + + NOTIFYICONIDENTIFIER nid; + ZeroMemory(&nid, sizeof(NOTIFYICONIDENTIFIER)); + nid.cbSize = sizeof(NOTIFYICONIDENTIFIER); + nid.hWnd = windows[MAIN_WINDOW_ID].hWnd; + nid.uID = p_id; + nid.guidItem = GUID_NULL; + + RECT rect; + if (Shell_NotifyIconGetRect(&nid, &rect) != S_OK) { + return Rect2(); + } + Rect2 ind_rect = Rect2(Point2(rect.left, rect.top) - _get_screens_origin(), Size2(rect.right - rect.left, rect.bottom - rect.top)); + for (int i = 0; i < get_screen_count(); i++) { + Rect2 screen_rect = Rect2(screen_get_position(i), screen_get_size(i)); + if (screen_rect.encloses(ind_rect)) { + return ind_rect; + } + } + return Rect2(); +} + void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) { ERR_FAIL_COND(!indicators.has(p_id)); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 12350d6b34..80f6061348 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -690,6 +690,7 @@ public: virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override; virtual void status_indicator_set_menu(IndicatorID p_id, const RID &p_rid) override; virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override; + virtual Rect2 status_indicator_get_rect(IndicatorID p_id) const override; virtual void delete_status_indicator(IndicatorID p_id) override; virtual void set_context(Context p_context) override; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 4fda49a877..49cfa8a030 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -4494,6 +4494,9 @@ void TextEdit::set_multiple_carets_enabled(bool p_enabled) { multi_carets_enabled = p_enabled; if (!multi_carets_enabled) { remove_secondary_carets(); + multicaret_edit_count = 0; + multicaret_edit_ignore_carets.clear(); + multicaret_edit_merge_queued = false; } } @@ -4568,6 +4571,9 @@ int TextEdit::get_caret_count() const { } void TextEdit::add_caret_at_carets(bool p_below) { + if (!multi_carets_enabled) { + return; + } const int last_line_max_wrap = get_line_wrap_count(text.size() - 1); begin_multicaret_edit(); @@ -4594,6 +4600,7 @@ void TextEdit::add_caret_at_carets(bool p_below) { // Add a new caret. int new_caret_index = add_caret(caret_line, caret_column); + ERR_FAIL_COND_MSG(new_caret_index < 0, "Failed to add a caret."); // Copy the selection origin and last fit. set_selection_origin_line(selection_origin_line, true, -1, new_caret_index); @@ -4733,7 +4740,7 @@ void TextEdit::collapse_carets(int p_from_line, int p_from_column, int p_to_line // Caret was in the collapsed area. set_caret_line(collapse_line, false, true, -1, i); set_caret_column(collapse_column, false, i); - if (is_in_mulitcaret_edit()) { + if (is_in_mulitcaret_edit() && get_caret_count() > 1) { multicaret_edit_ignore_carets.insert(i); } any_collapsed = true; @@ -4746,7 +4753,7 @@ void TextEdit::collapse_carets(int p_from_line, int p_from_column, int p_to_line deselect(i); set_caret_line(collapse_line, false, true, -1, i); set_caret_column(collapse_column, false, i); - if (is_in_mulitcaret_edit()) { + if (is_in_mulitcaret_edit() && get_caret_count() > 1) { multicaret_edit_ignore_carets.insert(i); } any_collapsed = true; @@ -4872,10 +4879,16 @@ void TextEdit::merge_overlapping_carets() { // Starts a multicaret edit operation. Call this before iterating over the carets and call [end_multicaret_edit] afterwards. void TextEdit::begin_multicaret_edit() { + if (!multi_carets_enabled) { + return; + } multicaret_edit_count++; } void TextEdit::end_multicaret_edit() { + if (!multi_carets_enabled) { + return; + } if (multicaret_edit_count > 0) { multicaret_edit_count--; } @@ -7060,7 +7073,7 @@ void TextEdit::_paste_internal(int p_caret) { begin_complex_operation(); begin_multicaret_edit(); Vector<int> sorted_carets = get_sorted_carets(); - for (int i = 0; i < get_caret_count(); i++) { + for (int i = 0; i < sorted_carets.size(); i++) { int caret_index = sorted_carets[i]; if (p_caret != -1 && p_caret != caret_index) { continue; diff --git a/scene/main/status_indicator.cpp b/scene/main/status_indicator.cpp index 22aa051c70..891974f68f 100644 --- a/scene/main/status_indicator.cpp +++ b/scene/main/status_indicator.cpp @@ -80,6 +80,7 @@ void StatusIndicator::_bind_methods() { ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible); ClassDB::bind_method(D_METHOD("set_menu", "menu"), &StatusIndicator::set_menu); ClassDB::bind_method(D_METHOD("get_menu"), &StatusIndicator::get_menu); + ClassDB::bind_method(D_METHOD("get_rect"), &StatusIndicator::get_rect); ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "mouse_position"))); @@ -182,3 +183,10 @@ void StatusIndicator::set_visible(bool p_visible) { bool StatusIndicator::is_visible() const { return visible; } + +Rect2 StatusIndicator::get_rect() const { + if (iid == DisplayServer::INVALID_INDICATOR_ID) { + return Rect2(); + } + return DisplayServer::get_singleton()->status_indicator_get_rect(iid); +} diff --git a/scene/main/status_indicator.h b/scene/main/status_indicator.h index cc137391a9..cd38da6e6c 100644 --- a/scene/main/status_indicator.h +++ b/scene/main/status_indicator.h @@ -61,6 +61,8 @@ public: void set_visible(bool p_visible); bool is_visible() const; + + Rect2 get_rect() const; }; #endif // STATUS_INDICATOR_H diff --git a/servers/display_server.cpp b/servers/display_server.cpp index f1e3479eae..fcbedbc1f8 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -730,6 +730,11 @@ void DisplayServer::status_indicator_set_callback(IndicatorID p_id, const Callab WARN_PRINT("Status indicator not supported by this display server."); } +Rect2 DisplayServer::status_indicator_get_rect(IndicatorID p_id) const { + WARN_PRINT("Status indicator not supported by this display server."); + return Rect2(); +} + void DisplayServer::delete_status_indicator(IndicatorID p_id) { WARN_PRINT("Status indicator not supported by this display server."); } @@ -983,6 +988,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("status_indicator_set_tooltip", "id", "tooltip"), &DisplayServer::status_indicator_set_tooltip); ClassDB::bind_method(D_METHOD("status_indicator_set_menu", "id", "menu_rid"), &DisplayServer::status_indicator_set_menu); ClassDB::bind_method(D_METHOD("status_indicator_set_callback", "id", "callback"), &DisplayServer::status_indicator_set_callback); + ClassDB::bind_method(D_METHOD("status_indicator_get_rect", "id"), &DisplayServer::status_indicator_get_rect); ClassDB::bind_method(D_METHOD("delete_status_indicator", "id"), &DisplayServer::delete_status_indicator); ClassDB::bind_method(D_METHOD("tablet_get_driver_count"), &DisplayServer::tablet_get_driver_count); diff --git a/servers/display_server.h b/servers/display_server.h index 0391edecd4..30f6ee5ccf 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -569,6 +569,7 @@ public: virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip); virtual void status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid); virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback); + virtual Rect2 status_indicator_get_rect(IndicatorID p_id) const; virtual void delete_status_indicator(IndicatorID p_id); enum Context { diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 246d869687..b2d9f5100e 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -7220,6 +7220,11 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { CHECK(text_edit->get_caret_count() == 1); CHECK(text_edit->get_caret_line(0) == 0); CHECK(text_edit->get_caret_column(0) == 0); + + // Does nothing if multiple carets are disabled. + text_edit->set_multiple_carets_enabled(false); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 1); } memdelete(text_edit); |