diff options
39 files changed, 623 insertions, 113 deletions
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 671b06f0ed..2e0ad94fcf 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1567,10 +1567,11 @@ String String::num_real(double p_num, bool p_trailing) { #else int decimals = 6; #endif - // We want to align the digits to the above sane default, so we only - // need to subtract log10 for numbers with a positive power of ten. - if (p_num > 10) { - decimals -= (int)floor(log10(p_num)); + // We want to align the digits to the above sane default, so we only need + // to subtract log10 for numbers with a positive power of ten magnitude. + double abs_num = Math::abs(p_num); + if (abs_num > 10) { + decimals -= (int)floor(log10(abs_num)); } return num(p_num, decimals); } diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h index 6171c8c88f..427a337aab 100644 --- a/core/variant/container_type_validate.h +++ b/core/variant/container_type_validate.h @@ -79,9 +79,12 @@ struct ContainerTypeValidate { return true; } - ERR_FAIL_COND_V_MSG(type != p_variant.get_type(), false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(p_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'."); if (type != p_variant.get_type()) { - return false; + if (p_variant.get_type() == Variant::NIL && type == Variant::OBJECT) { + return true; + } + + ERR_FAIL_V_MSG(false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(p_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'."); } if (type != Variant::OBJECT) { @@ -90,7 +93,7 @@ struct ContainerTypeValidate { #ifdef DEBUG_ENABLED ObjectID object_id = p_variant; if (object_id == ObjectID()) { - return true; //fine its null; + return true; // This is fine, it's null. } Object *object = ObjectDB::get_instance(object_id); ERR_FAIL_COND_V_MSG(object == nullptr, false, "Attempted to " + String(p_operation) + " an invalid (previously freed?) object instance into a '" + String(where) + "."); @@ -101,7 +104,7 @@ struct ContainerTypeValidate { } #endif if (class_name == StringName()) { - return true; //all good, no class type requested + return true; // All good, no class type requested. } StringName obj_class = object->get_class_name(); @@ -110,12 +113,12 @@ struct ContainerTypeValidate { } if (script.is_null()) { - return true; //all good + return true; // All good, no script requested. } Ref<Script> other_script = object->get_script(); - //check base script.. + // Check base script.. ERR_FAIL_COND_V_MSG(other_script.is_null(), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'."); ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'."); diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ff66affeab..a8080c70c6 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -568,6 +568,13 @@ <member name="display/window/ios/hide_home_indicator" type="bool" setter="" getter="" default="true"> If [code]true[/code], the home indicator is hidden automatically. This only affects iOS devices without a physical home button. </member> + <member name="display/window/ios/hide_status_bar" type="bool" setter="" getter="" default="true"> + If [code]true[/code], the status bar is hidden while the app is running. + </member> + <member name="display/window/ios/suppress_ui_gesture" type="bool" setter="" getter="" default="true"> + If [code]true[/code], it will require two swipes to access iOS UI that uses gestures. + [b]Note:[/b] This setting has no effect on the home indicator if [code]hide_home_indicator[/code] is [code]true[/code]. + </member> <member name="display/window/per_pixel_transparency/allowed" type="bool" setter="" getter="" default="false"> If [code]true[/code], allows per-pixel transparency for the window background. This affects performance, so leave it on [code]false[/code] unless you need it. See also [member display/window/size/transparent] and [member rendering/transparent_background]. </member> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 998e782975..fce94fe567 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -71,6 +71,12 @@ <description> </description> </method> + <method name="get_screen_transform" qualifiers="const"> + <return type="Transform2D" /> + <description> + Returns the transform from the Viewport's coordinates to the screen coordinates of the containing window manager window. + </description> + </method> <method name="get_texture" qualifiers="const"> <return type="ViewportTexture" /> <description> diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 4d3ffdc12b..421e8debed 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2435,6 +2435,7 @@ void Node3DEditorViewport::_notification(int p_what) { bool vp_visible = is_visible_in_tree(); set_process(vp_visible); + set_physics_process(vp_visible); if (vp_visible) { orthogonal = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL)); @@ -2658,6 +2659,21 @@ void Node3DEditorViewport::_notification(int p_what) { } } break; + case NOTIFICATION_PHYSICS_PROCESS: { + if (!update_preview_node) { + return; + } + if (preview_node->is_inside_tree()) { + preview_node_pos = _get_instance_position(preview_node_viewport_pos); + Transform3D preview_gl_transform = Transform3D(Basis(), preview_node_pos); + preview_node->set_global_transform(preview_gl_transform); + if (!preview_node->is_visible()) { + preview_node->show(); + } + } + update_preview_node = false; + } break; + case NOTIFICATION_ENTER_TREE: { surface->connect("draw", callable_mp(this, &Node3DEditorViewport::_draw)); surface->connect("gui_input", callable_mp(this, &Node3DEditorViewport::_sinput)); @@ -4029,7 +4045,7 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po gl_transform = parent_node3d->get_global_gizmo_transform(); } - gl_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point)); + gl_transform.origin = spatial_editor->snap_point(preview_node_pos); gl_transform.basis *= node3d->get_transform().basis; editor_data->get_undo_redo()->add_do_method(instantiated_scene, "set_global_transform", gl_transform); @@ -4093,7 +4109,9 @@ void Node3DEditorViewport::_perform_drop_data() { } } -bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { +bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + preview_node_viewport_pos = p_point; + bool can_instantiate = false; if (!preview_node->is_inside_tree() && spatial_editor->get_preview_material().is_null()) { @@ -4157,6 +4175,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant } if (can_instantiate) { _create_preview_node(files); + preview_node->hide(); } } } else { @@ -4166,8 +4185,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant } if (can_instantiate) { - Transform3D gl_transform = Transform3D(Basis(), _get_instance_position(p_point)); - preview_node->set_global_transform(gl_transform); + update_preview_node = true; return true; } @@ -6932,6 +6950,10 @@ HashSet<RID> _get_physics_bodies_rid(Node *node) { } void Node3DEditor::snap_selected_nodes_to_floor() { + do_snap_selected_nodes_to_floor = true; +} + +void Node3DEditor::_snap_selected_nodes_to_floor() { const List<Node *> &selection = editor_selection->get_selected_node_list(); Dictionary snap_data; @@ -7229,6 +7251,13 @@ void Node3DEditor::_notification(int p_what) { tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false); } } break; + + case NOTIFICATION_PHYSICS_PROCESS: { + if (do_snap_selected_nodes_to_floor) { + _snap_selected_nodes_to_floor(); + do_snap_selected_nodes_to_floor = false; + } + } } } @@ -8349,10 +8378,12 @@ void Node3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { spatial_editor->show(); spatial_editor->set_process(true); + spatial_editor->set_physics_process(true); } else { spatial_editor->hide(); spatial_editor->set_process(false); + spatial_editor->set_physics_process(false); } } diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index c76f534c22..6ff0fa3afe 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -193,6 +193,9 @@ private: void _menu_option(int p_option); void _set_auto_orthogonal(); Node3D *preview_node = nullptr; + bool update_preview_node = false; + Point2 preview_node_viewport_pos; + Vector3 preview_node_pos; AABB *preview_bounds = nullptr; Vector<String> selected_files; AcceptDialog *accept = nullptr; @@ -413,7 +416,7 @@ private: bool _create_instance(Node *parent, String &path, const Point2 &p_point); void _perform_drop_data(); - bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void _project_settings_changed(); @@ -720,6 +723,9 @@ private: void _selection_changed(); void _refresh_menu_icons(); + bool do_snap_selected_nodes_to_floor = false; + void _snap_selected_nodes_to_floor(); + // Preview Sun and Environment uint32_t world_env_count = 0; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 58a0160010..8607f98a90 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1978,18 +1978,6 @@ void ScriptTextEditor::_enable_code_editor() { add_child(connection_info_dialog); - edit_hb->add_child(search_menu); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); - search_menu->get_popup()->add_separator(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); - search_menu->get_popup()->add_separator(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL); - search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); - edit_hb->add_child(edit_menu); edit_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_prepare_edit_menu)); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); @@ -2000,38 +1988,73 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("line_menu"); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Line"), "line_menu"); + } + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("folding_menu"); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Folding"), "folding_menu"); + } edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT); + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("indent_menu"); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Indentation"), "indent_menu"); + } edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_separator(); - - edit_menu->get_popup()->add_child(convert_case); - edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); - convert_case->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); - + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("convert_case"); + sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); + sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); + sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); + } edit_menu->get_popup()->add_child(highlighter_menu); edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu"); highlighter_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter)); + edit_hb->add_child(search_menu); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); + search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); + search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL); + search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + _load_theme_settings(); edit_hb->add_child(goto_menu); @@ -2106,9 +2129,6 @@ ScriptTextEditor::ScriptTextEditor() { edit_menu->set_switch_on_hover(true); edit_menu->set_shortcut_context(this); - convert_case = memnew(PopupMenu); - convert_case->set_name("convert_case"); - highlighter_menu = memnew(PopupMenu); highlighter_menu->set_name("highlighter_menu"); @@ -2153,7 +2173,6 @@ ScriptTextEditor::~ScriptTextEditor() { memdelete(color_panel); memdelete(edit_hb); memdelete(edit_menu); - memdelete(convert_case); memdelete(highlighter_menu); memdelete(search_menu); memdelete(goto_menu); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index fb02e5c7c4..c165295a8e 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -79,7 +79,6 @@ class ScriptTextEditor : public ScriptEditorBase { PopupMenu *breakpoints_menu = nullptr; PopupMenu *highlighter_menu = nullptr; PopupMenu *context_menu = nullptr; - PopupMenu *convert_case = nullptr; GotoLineDialog *goto_line_dialog = nullptr; ScriptEditorQuickOpen *quick_open = nullptr; diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 364c3b7246..6fdc9a80e8 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -66,7 +66,9 @@ void TilesEditorPlugin::_thread() { pattern_preview_sem.wait(); pattern_preview_mutex.lock(); - if (pattern_preview_queue.size()) { + if (pattern_preview_queue.size() == 0) { + pattern_preview_mutex.unlock(); + } else { QueueItem item = pattern_preview_queue.front()->get(); pattern_preview_queue.pop_front(); pattern_preview_mutex.unlock(); @@ -130,8 +132,6 @@ void TilesEditorPlugin::_thread() { item.callback.callp(args_ptr, 2, r, error); viewport->queue_delete(); - } else { - pattern_preview_mutex.unlock(); } } } diff --git a/main/main.cpp b/main/main.cpp index c990e01ee3..bb6ab11362 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1808,6 +1808,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph "0,33200,1,or_greater")); // No negative numbers GLOBAL_DEF("display/window/ios/hide_home_indicator", true); + GLOBAL_DEF("display/window/ios/hide_status_bar", true); + GLOBAL_DEF("display/window/ios/suppress_ui_gesture", true); GLOBAL_DEF("input_devices/pointing/ios/touch_delay", 0.15); ProjectSettings::get_singleton()->set_custom_property_info("input_devices/pointing/ios/touch_delay", PropertyInfo(Variant::FLOAT, diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 6fbdec863f..898e4eb1a6 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3226,7 +3226,13 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); if (!ResourceLoader::exists(p_preload->resolved_path)) { - push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); + + if (file_check->file_exists(p_preload->resolved_path)) { + push_error(vformat(R"(Preload file "%s" has no resource loaders (unrecognized file extension).)", p_preload->resolved_path), p_preload->path); + } else { + push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + } } else { // TODO: Don't load if validating: use completion cache. p_preload->resource = ResourceLoader::load(p_preload->resolved_path); diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 4d1aa89ac9..8e48066623 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -9,6 +9,25 @@ <tutorials> <link title="GLTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link> </tutorials> + <methods> + <method name="get_additional_data"> + <return type="Variant" /> + <param index="0" name="extension_name" type="StringName" /> + <description> + Gets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + </description> + </method> + <method name="set_additional_data"> + <return type="void" /> + <param index="0" name="extension_name" type="StringName" /> + <param index="1" name="additional_data" type="Variant" /> + <description> + Sets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + </description> + </method> + </methods> <members> <member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1"> </member> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 56f3a70631..d0740cf7ca 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -20,6 +20,14 @@ <description> </description> </method> + <method name="get_additional_data"> + <return type="Variant" /> + <param index="0" name="extension_name" type="StringName" /> + <description> + Gets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + </description> + </method> <method name="get_animation_player"> <return type="AnimationPlayer" /> <param index="0" name="idx" type="int" /> @@ -120,6 +128,15 @@ <description> </description> </method> + <method name="set_additional_data"> + <return type="void" /> + <param index="0" name="extension_name" type="StringName" /> + <param index="1" name="additional_data" type="Variant" /> + <description> + Sets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + </description> + </method> <method name="set_animations"> <return type="void" /> <param index="0" name="animations" type="GLTFAnimation[]" /> diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index 60192c67e6..ac5665e396 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -87,6 +87,8 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("get_animations"), &GLTFState::get_animations); ClassDB::bind_method(D_METHOD("set_animations", "animations"), &GLTFState::set_animations); ClassDB::bind_method(D_METHOD("get_scene_node", "idx"), &GLTFState::get_scene_node); + ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFState::get_additional_data); + ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFState::set_additional_data); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int @@ -358,3 +360,11 @@ String GLTFState::get_base_path() { void GLTFState::set_base_path(String p_base_path) { base_path = p_base_path; } + +Variant GLTFState::get_additional_data(const StringName &p_extension_name) { + return additional_data[p_extension_name]; +} + +void GLTFState::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) { + additional_data[p_extension_name] = p_additional_data; +} diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index afe7e82010..e24017b0fd 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -97,6 +97,7 @@ class GLTFState : public Resource { HashMap<ObjectID, GLTFSkeletonIndex> skeleton3d_to_gltf_skeleton; HashMap<ObjectID, HashMap<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_gltf_skin; + Dictionary additional_data; protected: static void _bind_methods(); @@ -191,6 +192,9 @@ public: AnimationPlayer *get_animation_player(int idx); + Variant get_additional_data(const StringName &p_extension_name); + void set_additional_data(const StringName &p_extension_name, Variant p_additional_data); + //void set_scene_nodes(RBMap<GLTFNodeIndex, Node *> p_scene_nodes) { // this->scene_nodes = p_scene_nodes; //} diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp index 86280603fa..6fd36f93b7 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -57,6 +57,8 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light); ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light); + ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data); + ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data); ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int @@ -176,3 +178,11 @@ GLTFLightIndex GLTFNode::get_light() { void GLTFNode::set_light(GLTFLightIndex p_light) { light = p_light; } + +Variant GLTFNode::get_additional_data(const StringName &p_extension_name) { + return additional_data[p_extension_name]; +} + +void GLTFNode::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) { + additional_data[p_extension_name] = p_additional_data; +} diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h index 1a57ea32e2..90a4fa99ed 100644 --- a/modules/gltf/structures/gltf_node.h +++ b/modules/gltf/structures/gltf_node.h @@ -53,6 +53,7 @@ private: Vector3 scale = Vector3(1, 1, 1); Vector<int> children; GLTFLightIndex light = -1; + Dictionary additional_data; protected: static void _bind_methods(); @@ -96,6 +97,9 @@ public: GLTFLightIndex get_light(); void set_light(GLTFLightIndex p_light); + + Variant get_additional_data(const StringName &p_extension_name); + void set_additional_data(const StringName &p_extension_name, Variant p_additional_data); }; #endif // GLTF_NODE_H diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index df9985916b..8359580805 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -261,11 +261,11 @@ Error SceneReplicationInterface::_update_sync_visibility(int p_peer, Multiplayer if (p_peer == 0) { for (KeyValue<int, PeerInfo> &E : peers_info) { // Might be visible to this specific peer. - is_visible = is_visible || p_sync->is_visible_to(E.key); - if (is_visible == E.value.sync_nodes.has(sid)) { + bool is_visible_to_peer = is_visible || p_sync->is_visible_to(E.key); + if (is_visible_to_peer == E.value.sync_nodes.has(sid)) { continue; } - if (is_visible) { + if (is_visible_to_peer) { E.value.sync_nodes.insert(sid); } else { E.value.sync_nodes.erase(sid); diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 5ac167ad98..b5978ab134 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -96,6 +96,7 @@ env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_dep env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") env.modules_sources += module_obj diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 25bf496de9..f089fd066e 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -10,6 +10,19 @@ <tutorials> <link title="Setting up XR">$DOCS_URL/tutorials/xr/setting_up_xr.html</link> </tutorials> + <methods> + <method name="get_available_display_refresh_rates" qualifiers="const"> + <return type="Array" /> + <description> + Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized. + </description> + </method> + </methods> + <members> + <member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0"> + The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized. + </member> + </members> <signals> <signal name="pose_recentered"> <description> diff --git a/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.cpp b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.cpp new file mode 100644 index 0000000000..c0bbaea5b4 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.cpp @@ -0,0 +1,123 @@ +/*************************************************************************/ +/* openxr_fb_display_refresh_rate_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_fb_display_refresh_rate_extension.h" + +OpenXRDisplayRefreshRateExtension *OpenXRDisplayRefreshRateExtension::singleton = nullptr; + +OpenXRDisplayRefreshRateExtension *OpenXRDisplayRefreshRateExtension::get_singleton() { + return singleton; +} + +OpenXRDisplayRefreshRateExtension::OpenXRDisplayRefreshRateExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + // Extensions we use for our hand tracking. + request_extensions[XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME] = &display_refresh_rate_ext; +} + +OpenXRDisplayRefreshRateExtension::~OpenXRDisplayRefreshRateExtension() { + display_refresh_rate_ext = false; +} + +void OpenXRDisplayRefreshRateExtension::on_instance_created(const XrInstance p_instance) { + if (display_refresh_rate_ext) { + EXT_INIT_XR_FUNC(xrEnumerateDisplayRefreshRatesFB); + EXT_INIT_XR_FUNC(xrGetDisplayRefreshRateFB); + EXT_INIT_XR_FUNC(xrRequestDisplayRefreshRateFB); + } +} + +void OpenXRDisplayRefreshRateExtension::on_instance_destroyed() { + display_refresh_rate_ext = false; +} + +float OpenXRDisplayRefreshRateExtension::get_refresh_rate() const { + float refresh_rate = 0.0; + + if (display_refresh_rate_ext) { + float rate; + XrResult result = xrGetDisplayRefreshRateFB(openxr_api->get_session(), &rate); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain refresh rate [", openxr_api->get_error_string(result), "]"); + } else { + refresh_rate = rate; + } + } + + return refresh_rate; +} + +void OpenXRDisplayRefreshRateExtension::set_refresh_rate(float p_refresh_rate) { + if (display_refresh_rate_ext) { + XrResult result = xrRequestDisplayRefreshRateFB(openxr_api->get_session(), p_refresh_rate); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to set refresh rate [", openxr_api->get_error_string(result), "]"); + } + } +} + +Array OpenXRDisplayRefreshRateExtension::get_available_refresh_rates() const { + Array arr; + XrResult result; + + if (display_refresh_rate_ext) { + uint32_t display_refresh_rate_count = 0; + result = xrEnumerateDisplayRefreshRatesFB(openxr_api->get_session(), 0, &display_refresh_rate_count, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain refresh rates count [", openxr_api->get_error_string(result), "]"); + } + + if (display_refresh_rate_count > 0) { + float *display_refresh_rates = (float *)memalloc(sizeof(float) * display_refresh_rate_count); + if (display_refresh_rates == nullptr) { + print_line("OpenXR: Failed to obtain refresh rates memory buffer [", openxr_api->get_error_string(result), "]"); + return arr; + } + + result = xrEnumerateDisplayRefreshRatesFB(openxr_api->get_session(), display_refresh_rate_count, &display_refresh_rate_count, display_refresh_rates); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain refresh rates count [", openxr_api->get_error_string(result), "]"); + memfree(display_refresh_rates); + return arr; + } + + for (uint32_t i = 0; i < display_refresh_rate_count; i++) { + float refresh_rate = display_refresh_rates[i]; + arr.push_back(Variant(refresh_rate)); + } + + memfree(display_refresh_rates); + } + } + + return arr; +} diff --git a/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.h b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.h new file mode 100644 index 0000000000..dcd52fe4d1 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* openxr_fb_display_refresh_rate_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_FB_DISPLAY_REFRESH_RATE_EXTENSION_H +#define OPENXR_FB_DISPLAY_REFRESH_RATE_EXTENSION_H + +// This extension gives us access to the possible display refresh rates +// supported by the HMD. +// While this is an FB extension it has been adopted by most runtimes and +// will likely become core in the near future. + +#include "../openxr_api.h" +#include "../util.h" + +#include "openxr_extension_wrapper.h" + +class OpenXRDisplayRefreshRateExtension : public OpenXRExtensionWrapper { +public: + static OpenXRDisplayRefreshRateExtension *get_singleton(); + + OpenXRDisplayRefreshRateExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRDisplayRefreshRateExtension() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + float get_refresh_rate() const; + void set_refresh_rate(float p_refresh_rate); + + Array get_available_refresh_rates() const; + +private: + static OpenXRDisplayRefreshRateExtension *singleton; + + bool display_refresh_rate_ext = false; + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateDisplayRefreshRatesFB, (XrSession), session, (uint32_t), displayRefreshRateCapacityInput, (uint32_t *), displayRefreshRateCountOutput, (float *), displayRefreshRates); + EXT_PROTO_XRRESULT_FUNC2(xrGetDisplayRefreshRateFB, (XrSession), session, (float *), display_refresh_rate); + EXT_PROTO_XRRESULT_FUNC2(xrRequestDisplayRefreshRateFB, (XrSession), session, (float), display_refresh_rate); +}; + +#endif // OPENXR_FB_DISPLAY_REFRESH_RATE_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp index 88cc7c061c..4d996e6283 100644 --- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp @@ -69,6 +69,34 @@ bool OpenXRHTCViveTrackerExtension::on_event_polled(const XrEventDataBuffer &eve bool OpenXRHTCViveTrackerExtension::is_path_supported(const String &p_path) { if (p_path == "/interaction_profiles/htc/vive_tracker_htcx") { return available; + } else if (p_path == "/user/vive_tracker_htcx/role/handheld_object") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_foot") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_foot") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_shoulder") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_shoulder") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_elbow") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_elbow") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_knee") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_knee") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/waist") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/chest") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/chest") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/camera") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/keyboard") { + return available; } // Not a path under this extensions control, so we return true; diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 8a67462613..1ff1dac512 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -50,6 +50,7 @@ #endif #include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" @@ -443,12 +444,12 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType for (uint32_t i = 0; i < view_count; i++) { print_verbose("OpenXR: Found supported view configuration view"); - print_verbose(String(" - width: ") + view_configuration_views[i].maxImageRectWidth); - print_verbose(String(" - height: ") + view_configuration_views[i].maxImageRectHeight); - print_verbose(String(" - sample count: ") + view_configuration_views[i].maxSwapchainSampleCount); - print_verbose(String(" - recommended render width: ") + view_configuration_views[i].recommendedImageRectWidth); - print_verbose(String(" - recommended render height: ") + view_configuration_views[i].recommendedImageRectHeight); - print_verbose(String(" - recommended render sample count: ") + view_configuration_views[i].recommendedSwapchainSampleCount); + print_verbose(String(" - width: ") + itos(view_configuration_views[i].maxImageRectWidth)); + print_verbose(String(" - height: ") + itos(view_configuration_views[i].maxImageRectHeight)); + print_verbose(String(" - sample count: ") + itos(view_configuration_views[i].maxSwapchainSampleCount)); + print_verbose(String(" - recommended render width: ") + itos(view_configuration_views[i].recommendedImageRectWidth)); + print_verbose(String(" - recommended render height: ") + itos(view_configuration_views[i].recommendedImageRectHeight)); + print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount)); } return true; @@ -1748,6 +1749,31 @@ void OpenXRAPI::end_frame() { } } +float OpenXRAPI::get_display_refresh_rate() const { + OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton(); + if (drrext) { + return drrext->get_refresh_rate(); + } + + return 0.0; +} + +void OpenXRAPI::set_display_refresh_rate(float p_refresh_rate) { + OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton(); + if (drrext != nullptr) { + drrext->set_refresh_rate(p_refresh_rate); + } +} + +Array OpenXRAPI::get_available_display_refresh_rates() const { + OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton(); + if (drrext != nullptr) { + return drrext->get_available_refresh_rates(); + } + + return Array(); +} + OpenXRAPI::OpenXRAPI() { // OpenXRAPI is only constructed if OpenXR is enabled. singleton = this; @@ -1817,6 +1843,7 @@ OpenXRAPI::OpenXRAPI() { register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension(this))); register_extension_wrapper(memnew(OpenXRHandTrackingExtension(this))); register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper(this))); + register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension(this))); } OpenXRAPI::~OpenXRAPI() { diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index bd69432dcb..5dce749351 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -84,8 +84,6 @@ private: bool ext_vive_focus3_available = false; bool ext_huawei_controller_available = false; - bool is_path_supported(const String &p_path); - // composition layer providers Vector<OpenXRCompositionLayerProvider *> composition_layer_providers; @@ -302,6 +300,7 @@ public: void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); bool xr_result(XrResult result, const char *format, Array args = Array()) const; + bool is_path_supported(const String &p_path); static bool openxr_is_enabled(bool p_check_run_in_editor = true); static OpenXRAPI *get_singleton(); @@ -336,6 +335,11 @@ public: void post_draw_viewport(RID p_render_target); void end_frame(); + // Display refresh rate + float get_display_refresh_rate() const; + void set_display_refresh_rate(float p_refresh_rate); + Array get_available_display_refresh_rates() const; + // action map String get_default_action_map_resource_name(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index e9de124f70..bdf437b0b7 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -41,6 +41,13 @@ void OpenXRInterface::_bind_methods() { ADD_SIGNAL(MethodInfo("session_focussed")); ADD_SIGNAL(MethodInfo("session_visible")); ADD_SIGNAL(MethodInfo("pose_recentered")); + + // Display refresh rate + ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &OpenXRInterface::get_display_refresh_rate); + ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + + ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); } StringName OpenXRInterface::get_name() const { @@ -147,24 +154,25 @@ void OpenXRInterface::_load_action_map() { Ref<OpenXRAction> xr_action = actions[j]; PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); - Vector<Tracker *> trackers_new; + Vector<Tracker *> trackers_for_action; for (int k = 0; k < toplevel_paths.size(); k++) { - Tracker *tracker = find_tracker(toplevel_paths[k], true); - if (tracker) { - trackers_new.push_back(tracker); + // Only check for our tracker if our path is supported. + if (openxr_api->is_path_supported(toplevel_paths[k])) { + Tracker *tracker = find_tracker(toplevel_paths[k], true); + if (tracker) { + trackers_for_action.push_back(tracker); + } } } - Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers); - if (action) { - // we link our actions back to our trackers so we know which actions to check when we're processing our trackers - for (int t = 0; t < trackers_new.size(); t++) { - link_action_to_tracker(trackers_new[t], action); + // Only add our action if we have atleast one valid toplevel path + if (trackers_for_action.size() > 0) { + Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers_for_action); + if (action) { + // add this to our map for creating our interaction profiles + xr_actions[xr_action] = action; } - - // add this to our map for creating our interaction profiles - xr_actions[xr_action] = action; } } } @@ -282,6 +290,13 @@ OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, tracker_rids); p_action_set->actions.push_back(action); + // we link our actions back to our trackers so we know which actions to check when we're processing our trackers + for (int i = 0; i < p_trackers.size(); i++) { + if (p_trackers[i]->actions.find(action) == -1) { + p_trackers[i]->actions.push_back(action); + } + } + return action; } @@ -330,6 +345,8 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_ return nullptr; } + ERR_FAIL_COND_V(!openxr_api->is_path_supported(p_tracker_name), nullptr); + // Create our RID RID tracker_rid = openxr_api->tracker_create(p_tracker_name); ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); @@ -389,12 +406,6 @@ void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_p } } -void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { - if (p_tracker->actions.find(p_action) == -1) { - p_tracker->actions.push_back(p_action); - } -} - void OpenXRInterface::handle_tracker(Tracker *p_tracker) { ERR_FAIL_NULL(openxr_api); ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); @@ -580,6 +591,36 @@ bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { return false; } +float OpenXRInterface::get_display_refresh_rate() const { + if (openxr_api == nullptr) { + return 0.0; + } else if (!openxr_api->is_initialized()) { + return 0.0; + } else { + return openxr_api->get_display_refresh_rate(); + } +} + +void OpenXRInterface::set_display_refresh_rate(float p_refresh_rate) { + if (openxr_api == nullptr) { + return; + } else if (!openxr_api->is_initialized()) { + return; + } else { + openxr_api->set_display_refresh_rate(p_refresh_rate); + } +} + +Array OpenXRInterface::get_available_display_refresh_rates() const { + if (openxr_api == nullptr) { + return Array(); + } else if (!openxr_api->is_initialized()) { + return Array(); + } else { + return openxr_api->get_available_display_refresh_rates(); + } +} + Size2 OpenXRInterface::get_render_target_size() { if (openxr_api == nullptr) { return Size2(); diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 72935b039c..454612346f 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -91,7 +91,6 @@ private: void free_actions(ActionSet *p_action_set); Tracker *find_tracker(const String &p_tracker_name, bool p_create = false); - void link_action_to_tracker(Tracker *p_tracker, Action *p_action); void handle_tracker(Tracker *p_tracker); void free_trackers(); @@ -120,6 +119,10 @@ public: virtual XRInterface::PlayAreaMode get_play_area_mode() const override; virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + float get_display_refresh_rate() const; + void set_display_refresh_rate(float p_refresh_rate); + Array get_available_display_refresh_rates() const; + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index eb398f77c7..0929d3a2b0 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -810,12 +810,12 @@ _FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_ ret.y = 0x7fffffff; ret.x = 0; + const int *ct_offsets_ptr = ct.offsets.ptr(); for (int j = 0; j < ct.texture_w - mw; j++) { int max_y = 0; - for (int k = j; k < j + mw; k++) { - int y = ct.offsets[k]; + int y = ct_offsets_ptr[k]; if (y > max_y) { max_y = y; } @@ -1393,7 +1393,10 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f int error = 0; if (!ft_library) { error = FT_Init_FreeType(&ft_library); - ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + if (error != 0) { + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -1422,6 +1425,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f if (error) { FT_Done_Face(fd->face); fd->face = nullptr; + memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); } @@ -1835,6 +1839,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f FT_Done_MM_Var(ft_library, amaster); } #else + memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } else { diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 13c997c981..4a46e17868 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -233,12 +233,13 @@ _FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_ ret.y = 0x7fffffff; ret.x = 0; + const int *ct_offsets_ptr = ct.offsets.ptr(); for (int j = 0; j < ct.texture_w - mw; j++) { int max_y = 0; for (int k = j; k < j + mw; k++) { - int y = ct.offsets[k]; + int y = ct_offsets_ptr[k]; if (y > max_y) { max_y = y; } @@ -818,7 +819,10 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f int error = 0; if (!ft_library) { error = FT_Init_FreeType(&ft_library); - ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + if (error != 0) { + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -847,6 +851,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f if (error) { FT_Done_Face(fd->face); fd->face = nullptr; + memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); } @@ -945,6 +950,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f FT_Done_MM_Var(ft_library, amaster); } #else + memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 4930b178ec..84e022182e 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -320,7 +320,9 @@ void WSLPeer::_do_client_handshake() { } tcp->poll(); - if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + if (tcp->get_status() == StreamPeerTCP::STATUS_CONNECTING) { + return; // Keep connecting. + } else if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { close(-1); // Failed to connect. return; } @@ -511,7 +513,7 @@ Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Ce resolver.start(host, port); resolver.try_next_candidate(tcp); - if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTING && !resolver.has_more_candidates()) { + if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTING && tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED && !resolver.has_more_candidates()) { _clear(); return FAILED; } diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 317f111202..6426f95b42 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2901,20 +2901,22 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP _load_image_data(splash_bg_color_image, data); } - for (int i = 0; i < icon_densities_count; ++i) { - if (main_image.is_valid() && !main_image->is_empty()) { - if (file == launcher_icons[i].export_path) { - _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); + if (file.ends_with(".png") && file.contains("mipmap")) { + for (int i = 0; i < icon_densities_count; ++i) { + if (main_image.is_valid() && !main_image->is_empty()) { + if (file == launcher_icons[i].export_path) { + _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); + } } - } - if (foreground.is_valid() && !foreground->is_empty()) { - if (file == launcher_adaptive_icon_foregrounds[i].export_path) { - _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); + if (foreground.is_valid() && !foreground->is_empty()) { + if (file == launcher_adaptive_icon_foregrounds[i].export_path) { + _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); + } } - } - if (background.is_valid() && !background->is_empty()) { - if (file == launcher_adaptive_icon_backgrounds[i].export_path) { - _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); + if (background.is_valid() && !background->is_empty()) { + if (file == launcher_adaptive_icon_backgrounds[i].export_path) { + _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); + } } } } diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm index 43669d3f94..53c5d3d0b4 100644 --- a/platform/ios/view_controller.mm +++ b/platform/ios/view_controller.mm @@ -164,7 +164,11 @@ // MARK: Orientation - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { - return UIRectEdgeAll; + if (GLOBAL_GET("display/window/ios/suppress_ui_gesture")) { + return UIRectEdgeAll; + } else { + return UIRectEdgeNone; + } } - (BOOL)shouldAutorotate { @@ -206,7 +210,11 @@ } - (BOOL)prefersStatusBarHidden { - return YES; + if (GLOBAL_GET("display/window/ios/hide_status_bar")) { + return YES; + } else { + return NO; + } } - (BOOL)prefersHomeIndicatorAutoHidden { diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 3b773685c3..bc767a47e5 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3087,7 +3087,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA rect_changed = true; } #if defined(VULKAN_ENABLED) - if (context_vulkan && window_created) { + if (context_vulkan && window.context_created) { // Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed. context_vulkan->window_resize(window_id, window.width, window.height); } @@ -3547,6 +3547,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, windows.erase(id); ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window."); } + wd.context_created = true; } #endif diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 6403b57d8d..53cde001ae 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -369,6 +369,7 @@ class DisplayServerWindows : public DisplayServer { bool no_focus = false; bool window_has_focus = false; bool exclusive = false; + bool context_created = false; // Used to transfer data between events using timer. WPARAM saved_wparam; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 4cd244306c..90974e31df 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -3863,7 +3863,7 @@ void TextEdit::undo() { } caret_pos_dirty = true; } - queue_redraw(); + adjust_viewport_to_caret(); } void TextEdit::redo() { @@ -3915,7 +3915,7 @@ void TextEdit::redo() { } caret_pos_dirty = true; } - queue_redraw(); + adjust_viewport_to_caret(); } void TextEdit::clear_undo_history() { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index f66337a2f9..a2963eadd5 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -3729,6 +3729,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_global_canvas_transform", "xform"), &Viewport::set_global_canvas_transform); ClassDB::bind_method(D_METHOD("get_global_canvas_transform"), &Viewport::get_global_canvas_transform); ClassDB::bind_method(D_METHOD("get_final_transform"), &Viewport::get_final_transform); + ClassDB::bind_method(D_METHOD("get_screen_transform"), &Viewport::get_screen_transform); ClassDB::bind_method(D_METHOD("get_visible_rect"), &Viewport::get_visible_rect); ClassDB::bind_method(D_METHOD("set_transparent_background", "enable"), &Viewport::set_transparent_background); diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index de13912b75..a4c958d402 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -7264,7 +7264,7 @@ String VisualShaderNodeProximityFade::generate_code(Shader::Mode p_mode, VisualS } else { code += " vec4 __depth_world_pos = INV_PROJECTION_MATRIX * vec4(vec3(SCREEN_UV, __depth_tex) * 2.0 - 1.0, 1.0);\n"; } - code += " __depth_world_pos.xyz /= __depth_world_pos.z;\n"; + code += " __depth_world_pos.xyz /= __depth_world_pos.w;\n"; code += vformat(" %s = clamp(1.0 - smoothstep(__depth_world_pos.z + %s, __depth_world_pos.z, VERTEX.z), 0.0, 1.0);\n", p_output_vars[0], p_input_vars[0]); return code; diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index cf34caac28..cd1b421ce8 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -411,9 +411,13 @@ TEST_CASE("[String] Number to string") { CHECK(String::num_real(3.141593) == "3.141593"); CHECK(String::num_real(3.141) == "3.141"); // No trailing zeros. #ifdef REAL_T_IS_DOUBLE + CHECK_MESSAGE(String::num_real(123.456789) == "123.456789", "Prints the appropriate amount of digits for real_t = double."); + CHECK_MESSAGE(String::num_real(-123.456789) == "-123.456789", "Prints the appropriate amount of digits for real_t = double."); CHECK_MESSAGE(String::num_real(Math_PI) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double."); CHECK_MESSAGE(String::num_real(3.1415f) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero."); #else + CHECK_MESSAGE(String::num_real(123.456789) == "123.4568", "Prints the appropriate amount of digits for real_t = float."); + CHECK_MESSAGE(String::num_real(-123.456789) == "-123.4568", "Prints the appropriate amount of digits for real_t = float."); CHECK_MESSAGE(String::num_real(Math_PI) == "3.141593", "Prints the appropriate amount of digits for real_t = float."); CHECK_MESSAGE(String::num_real(3.1415f) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float."); #endif // REAL_T_IS_DOUBLE diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 3a20ac123b..2ef0a3345f 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -3918,6 +3918,32 @@ TEST_CASE("[SceneTree][TextEdit] viewport") { CHECK(text_edit->get_last_full_visible_line_wrap_index() == 0); CHECK(text_edit->get_caret_wrap_index() == 0); + // Typing and undo / redo should adjust viewport + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + text_edit->set_line_as_first_visible(5); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_first_visible_line() == 5); + + SEND_GUI_KEY_EVENT(text_edit, Key::A); + CHECK(text_edit->get_first_visible_line() == 0); + + text_edit->set_line_as_first_visible(5); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_first_visible_line() == 5); + + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_first_visible_line() == 0); + + text_edit->set_line_as_first_visible(5); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_first_visible_line() == 5); + + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_first_visible_line() == 0); + memdelete(text_edit); } |