diff options
61 files changed, 1195 insertions, 298 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index a90cf6e361..974fd5283b 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1372,6 +1372,19 @@ ProjectSettings::ProjectSettings() { CRASH_COND_MSG(singleton != nullptr, "Instantiating a new ProjectSettings singleton is not supported."); singleton = this; +#ifdef TOOLS_ENABLED + // Available only at runtime in editor builds. Needs to be processed before anything else to work properly. + if (!Engine::get_singleton()->is_editor_hint()) { + String editor_features = OS::get_singleton()->get_environment("GODOT_EDITOR_CUSTOM_FEATURES"); + if (!editor_features.is_empty()) { + PackedStringArray feature_list = editor_features.split(","); + for (const String &s : feature_list) { + custom_features.insert(s); + } + } + } +#endif + GLOBAL_DEF_BASIC("application/config/name", ""); GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), ""); diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 2fb3bda87d..64b47ad19d 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -341,7 +341,15 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { if (load_task.resource.is_valid()) { if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - load_task.resource->set_path(load_task.local_path); + if (load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE) { + Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path); + if (old_res.is_valid() && old_res != load_task.resource) { + // If resource is already loaded, only replace its data, to avoid existing invalidating instances. + old_res->copy_from(load_task.resource); + load_task.resource = old_res; + } + } + load_task.resource->set_path(load_task.local_path, load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); } else if (!load_task.local_path.is_resource_file()) { load_task.resource->set_path_cache(load_task.local_path); } @@ -362,6 +370,17 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { if (_loaded_callback) { _loaded_callback(load_task.resource, load_task.local_path); } + } else if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + Ref<Resource> existing = ResourceCache::get_ref(load_task.local_path); + if (existing.is_valid()) { + load_task.resource = existing; + load_task.status = THREAD_LOAD_LOADED; + load_task.progress = 1.0; + + if (_loaded_callback) { + _loaded_callback(load_task.resource, load_task.local_path); + } + } } thread_load_mutex.unlock(); @@ -464,7 +483,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, load_task.type_hint = p_type_hint; load_task.cache_mode = p_cache_mode; load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE; - if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + if (p_cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) { Ref<Resource> existing = ResourceCache::get_ref(local_path); if (existing.is_valid()) { //referencing is fine diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 09bb20af79..44f24a97bb 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -136,7 +136,7 @@ <description> Displays OS native dialog for selecting files or directories in the file system. Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code]. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include Linux (X11 and Wayland), Windows, and macOS. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. [b]Note:[/b] [param current_directory] might be ignored. [b]Note:[/b] On Linux, [param show_hidden] is ignored. [b]Note:[/b] On macOS, native file dialogs have no title. @@ -161,7 +161,7 @@ - [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used. - [code]"default"[/code] - default selected option index ([int]) or default boolean value ([bool]). Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code]. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include Linux (X11 and Wayland), Windows, and macOS. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. [b]Note:[/b] [param current_directory] might be ignored. [b]Note:[/b] On Linux (X11), [param show_hidden] is ignored. [b]Note:[/b] On macOS, native file dialogs have no title. @@ -182,6 +182,13 @@ [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> + <method name="get_base_color" qualifiers="const"> + <return type="Color" /> + <description> + Returns the OS theme base color (default control background). Returns [code]Color(0, 0, 0, 0)[/code] if the base color is unknown. + [b]Note:[/b] This method is implemented on macOS and Windows. + </description> + </method> <method name="get_display_cutouts" qualifiers="const"> <return type="Rect2[]" /> <description> @@ -875,6 +882,17 @@ Returns [code]true[/code] if the specified [param feature] is supported by the current [DisplayServer], [code]false[/code] otherwise. </description> </method> + <method name="help_set_search_callbacks"> + <return type="void" /> + <param index="0" name="search_callback" type="Callable" /> + <param index="1" name="action_callback" type="Callable" /> + <description> + Sets native help system search callbacks. + [param search_callback] has the following arguments: [code]String search_string, int result_limit[/code] and return a [Dictionary] with "key, display name" pairs for the search results. Called when the user enters search terms in the [code]Help[/code] menu. + [param action_callback] has the following arguments: [code]String key[/code]. Called when the user selects a search result in the [code]Help[/code] menu. + [b]Note:[/b] This method is implemented only on macOS. + </description> + </method> <method name="ime_get_selection" qualifiers="const"> <return type="Vector2i" /> <description> @@ -1136,6 +1154,14 @@ Sets the window icon (usually displayed in the top-left corner) in the operating system's [i]native[/i] format. The file at [param filename] must be in [code].ico[/code] format on Windows or [code].icns[/code] on macOS. By using specially crafted [code].ico[/code] or [code].icns[/code] icons, [method set_native_icon] allows specifying different icons depending on the size the icon is displayed at. This size is determined by the operating system and user preferences (including the display scale factor). To use icons in other formats, use [method set_icon] instead. </description> </method> + <method name="set_system_theme_change_callback"> + <return type="void" /> + <param index="0" name="callable" type="Callable" /> + <description> + Sets the [param callable] that should be called when system theme settings are changed. Callback method should have zero arguments. + [b]Note:[/b] This method is implemented on Android, iOS, macOS, Windows, and Linux (X11/Wayland). + </description> + </method> <method name="status_indicator_set_callback"> <return type="void" /> <param index="0" name="id" type="int" /> @@ -1791,6 +1817,9 @@ <constant name="FEATURE_STATUS_INDICATOR" value="22" enum="Feature"> Display server supports application status indicators. </constant> + <constant name="FEATURE_NATIVE_HELP" value="23" enum="Feature"> + Display server supports native help system search callbacks. See [method help_set_search_callbacks]. + </constant> <constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode"> Makes the mouse cursor visible if it is hidden. </constant> diff --git a/doc/classes/InputEventMouseButton.xml b/doc/classes/InputEventMouseButton.xml index 1e86ade51e..9bcd1fc7bf 100644 --- a/doc/classes/InputEventMouseButton.xml +++ b/doc/classes/InputEventMouseButton.xml @@ -5,6 +5,7 @@ </brief_description> <description> Stores information about mouse click events. See [method Node._input]. + [b]Note:[/b] On Wear OS devices, rotary input is mapped to [constant MOUSE_BUTTON_WHEEL_UP] and [constant MOUSE_BUTTON_WHEEL_DOWN]. This can be changed to [constant MOUSE_BUTTON_WHEEL_LEFT] and [constant MOUSE_BUTTON_WHEEL_RIGHT] with the [member ProjectSettings.input_devices/pointing/android/rotary_input_scroll_axis] setting. </description> <tutorials> <link title="Using InputEvent">$DOCS_URL/tutorials/inputs/inputevent.html</link> diff --git a/doc/classes/MissingNode.xml b/doc/classes/MissingNode.xml index 4e528206cc..9171c8321f 100644 --- a/doc/classes/MissingNode.xml +++ b/doc/classes/MissingNode.xml @@ -13,8 +13,11 @@ <member name="original_class" type="String" setter="set_original_class" getter="get_original_class"> The name of the class this node was supposed to be (see [method Object.get_class]). </member> + <member name="original_scene" type="String" setter="set_original_scene" getter="get_original_scene"> + Returns the path of the scene this node was instance of originally. + </member> <member name="recording_properties" type="bool" setter="set_recording_properties" getter="is_recording_properties"> - If set to [code]true[/code], allows new properties to be added on top of the existing ones with [method Object.set]. + If [code]true[/code], allows new properties to be set along with existing ones. If [code]false[/code], only existing properties' values can be set, and new properties cannot be added. </member> </members> </class> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b23d605d9e..f69faff2bd 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1361,6 +1361,9 @@ <member name="input_devices/pointing/android/enable_pan_and_scale_gestures" type="bool" setter="" getter="" default="false"> If [code]true[/code], multi-touch pan and scale gestures are enabled on Android devices. </member> + <member name="input_devices/pointing/android/rotary_input_scroll_axis" type="int" setter="" getter="" default="1"> + On Wear OS devices, defines which axis of the mouse wheel rotary input is mapped to. This rotary input is usually performed by rotating the physical or virtual (touch-based) bezel on a smartwatch. + </member> <member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter="" default="true"> If [code]true[/code], sends mouse input events when tapping or swiping on the touchscreen. </member> diff --git a/doc/classes/ResourceImporterImageFont.xml b/doc/classes/ResourceImporterImageFont.xml index 481a6757d5..afc791f874 100644 --- a/doc/classes/ResourceImporterImageFont.xml +++ b/doc/classes/ResourceImporterImageFont.xml @@ -11,12 +11,16 @@ <link title="Bitmap fonts - Using fonts">$DOCS_URL/tutorials/ui/gui_using_fonts.html#bitmap-fonts</link> </tutorials> <members> + <member name="ascent" type="int" setter="" getter="" default="0"> + Font ascent (number of pixels above the baseline). If set to [code]0[/code], half of the character height is used. + </member> <member name="character_margin" type="Rect2i" setter="" getter="" default="Rect2i(0, 0, 0, 0)"> Margin applied around every imported glyph. If your font image contains guides (in the form of lines between glyphs) or if spacing between characters appears incorrect, try adjusting [member character_margin]. </member> <member name="character_ranges" type="PackedStringArray" setter="" getter="" default="PackedStringArray()"> - The character ranges to import from the font image. This is an array that maps each position on the image (in tile coordinates, not pixels). The font atlas is traversed from left to right and top to bottom. Characters can be specified with decimal numbers (127), hexadecimal numbers ([code]0x007f[/code]) or between single quotes ([code]'~'[/code]). Ranges can be specified with a hyphen between characters. - For instance, [code]0-127[/code] (or [code]0x0000-0x007f[/code]) denotes the full ASCII range. As another example, [code]' '-'~'[/code] is equivalent to [code]32-127[/code] and denotes the range of printable (visible) ASCII characters. + The character ranges to import from the font image. This is an array that maps each position on the image (in tile coordinates, not pixels). The font atlas is traversed from left to right and top to bottom. Characters can be specified with decimal numbers (127), hexadecimal numbers ([code]0x007f[/code], or [code]U+007f[/code]) or between single quotes ([code]'~'[/code]). Ranges can be specified with a hyphen between characters. + For example, [code]0-127[/code] represents the full ASCII range. It can also be written as [code]0x0000-0x007f[/code] (or [code]U+0000-U+007f[/code]). As another example, [code]' '-'~'[/code] is equivalent to [code]32-127[/code] and represents the range of printable (visible) ASCII characters. + For any range, the character advance and offset can be customized by appending three space-separated integer values (additional advance, x offset, y offset) to the end. For example [code]'a'-'b' 4 5 2[/code] sets the advance to [code]char_width + 4[/code] and offset to [code]Vector2(5, 2)[/code] for both `a` and `b` characters. Make sure [member character_ranges] doesn't exceed the number of [member columns] * [member rows] defined. Otherwise, the font will fail to import. </member> <member name="columns" type="int" setter="" getter="" default="1"> @@ -25,12 +29,19 @@ <member name="compress" type="bool" setter="" getter="" default="true"> If [code]true[/code], uses lossless compression for the resulting font. </member> + <member name="descent" type="int" setter="" getter="" default="0"> + Font descent (number of pixels below the baseline). If set to [code]0[/code], half of the character height is used. + </member> <member name="fallbacks" type="Array" setter="" getter="" default="[]"> List of font fallbacks to use if a glyph isn't found in this bitmap font. Fonts at the beginning of the array are attempted first. </member> <member name="image_margin" type="Rect2i" setter="" getter="" default="Rect2i(0, 0, 0, 0)"> Margin to cut on the sides of the entire image. This can be used to cut parts of the image that contain attribution information or similar. </member> + <member name="kerning_pairs" type="PackedStringArray" setter="" getter="" default="PackedStringArray()"> + Kerning pairs for the font. Kerning pair adjust the spacing between two characters. + Each string consist of three space separated values: "from" string, "to" string and integer offset. Each combination form the two string for a kerning pair, e.g, [code]ab cd -3[/code] will create kerning pairs [code]ac[/code], [code]ad[/code], [code]bc[/code], and [code]bd[/code] with offset [code]-3[/code]. + </member> <member name="rows" type="int" setter="" getter="" default="1"> Number of rows in the font image. See also [member columns]. </member> diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 83f31d122c..91bb676711 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -404,8 +404,8 @@ private: RID_Owner<CanvasTexture, true> canvas_texture_owner; /* Texture API */ - - mutable RID_Owner<Texture> texture_owner; + // Textures can be created from threads, so this RID_Owner is thread safe. + mutable RID_Owner<Texture, true> texture_owner; Ref<Image> _get_gl_image_and_format(const Ref<Image> &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type, bool &r_compressed, bool p_force_decompress) const; diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 34ca653ff7..b69faa7152 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -1808,17 +1808,8 @@ void AnimationBezierTrackEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after); ClassDB::bind_method(D_METHOD("_bezier_track_insert_key"), &AnimationBezierTrackEdit::_bezier_track_insert_key); - ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"))); - ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); - ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "offset"))); ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("clear_selection")); - ADD_SIGNAL(MethodInfo("close_request")); - - ADD_SIGNAL(MethodInfo("move_selection_begin")); - ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::FLOAT, "offset"))); - ADD_SIGNAL(MethodInfo("move_selection_commit")); - ADD_SIGNAL(MethodInfo("move_selection_cancel")); } AnimationBezierTrackEdit::AnimationBezierTrackEdit() { diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 8b59abc713..47c2df9d0f 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -6856,7 +6856,6 @@ AnimationTrackEditor::AnimationTrackEditor() { bezier_edit->set_timeline(timeline); bezier_edit->hide(); bezier_edit->set_v_size_flags(SIZE_EXPAND_FILL); - bezier_edit->connect("close_request", callable_mp(this, &AnimationTrackEditor::_cancel_bezier_edit)); timeline_vbox->set_custom_minimum_size(Size2(0, 150) * EDSCALE); diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 2e0dcb1e13..7ece509731 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -37,6 +37,101 @@ #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" +bool EditorHelpSearch::_all_terms_in_name(const Vector<String> &p_terms, const String &p_name) const { + for (int i = 0; i < p_terms.size(); i++) { + if (p_name.findn(p_terms[i]) < 0) { + return false; + } + } + return true; +} + +void EditorHelpSearch::_match_method_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::MethodDoc> &p_methods, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const { + // Constructors, Methods, Operators... + for (int i = 0; i < p_methods.size(); i++) { + String method_name = p_methods[i].name.to_lower(); + if (_all_terms_in_name(p_terms, method_name) || + (p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) || + (p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) || + (p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) { + r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_methods[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_methods[i].name); + } + } +} + +void EditorHelpSearch::_match_const_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ConstantDoc> &p_constants, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const { + for (int i = 0; i < p_constants.size(); i++) { + String method_name = p_constants[i].name.to_lower(); + if (_all_terms_in_name(p_terms, method_name) || + (p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) || + (p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) || + (p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) { + r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_constants[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_constants[i].name); + } + } +} + +void EditorHelpSearch::_match_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::PropertyDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const { + for (int i = 0; i < p_properties.size(); i++) { + String method_name = p_properties[i].name.to_lower(); + if (_all_terms_in_name(p_terms, method_name) || + (p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) || + (p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) || + (p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) { + r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_properties[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_properties[i].name); + } + } +} + +void EditorHelpSearch::_match_theme_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ThemeItemDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const { + for (int i = 0; i < p_properties.size(); i++) { + String method_name = p_properties[i].name.to_lower(); + if (_all_terms_in_name(p_terms, method_name) || + (p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) || + (p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) || + (p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) { + r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_properties[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_properties[i].name); + } + } +} + +Dictionary EditorHelpSearch::_native_search_cb(const String &p_search_string, int p_result_limit) { + Dictionary ret; + const String &term = p_search_string.strip_edges().to_lower(); + Vector<String> terms = term.split_spaces(); + if (terms.is_empty()) { + terms.append(term); + } + + for (HashMap<String, DocData::ClassDoc>::Iterator iterator_doc = EditorHelp::get_doc_data()->class_list.begin(); iterator_doc; ++iterator_doc) { + DocData::ClassDoc &class_doc = iterator_doc->value; + if (class_doc.name.is_empty()) { + continue; + } + if (class_doc.name.findn(term) > -1) { + ret[vformat("class_name:%s", class_doc.name)] = class_doc.name; + } + if (term.length() > 1 || term == "@") { + _match_method_name_and_push_back(term, terms, class_doc.constructors, TTRC("Constructor"), "method", class_doc.name, ret); + _match_method_name_and_push_back(term, terms, class_doc.methods, TTRC("Method"), "method", class_doc.name, ret); + _match_method_name_and_push_back(term, terms, class_doc.operators, TTRC("Operator"), "method", class_doc.name, ret); + _match_method_name_and_push_back(term, terms, class_doc.signals, TTRC("Signal"), "signal", class_doc.name, ret); + _match_const_name_and_push_back(term, terms, class_doc.constants, TTRC("Constant"), "constant", class_doc.name, ret); + _match_property_name_and_push_back(term, terms, class_doc.properties, TTRC("Property"), "property", class_doc.name, ret); + _match_theme_property_name_and_push_back(term, terms, class_doc.theme_properties, TTRC("Theme Property"), "theme_item", class_doc.name, ret); + _match_method_name_and_push_back(term, terms, class_doc.annotations, TTRC("Annotation"), "annotation", class_doc.name, ret); + } + if (ret.size() > p_result_limit) { + break; + } + } + return ret; +} + +void EditorHelpSearch::_native_action_cb(const String &p_item_string) { + emit_signal(SNAME("go_to_help"), p_item_string); +} + void EditorHelpSearch::_update_results() { String term = search_box->get_text(); @@ -94,6 +189,18 @@ void EditorHelpSearch::_confirmed() { void EditorHelpSearch::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_HELP)) { + DisplayServer::get_singleton()->help_set_search_callbacks(callable_mp(this, &EditorHelpSearch::_native_search_cb), callable_mp(this, &EditorHelpSearch::_native_action_cb)); + } + } break; + + case NOTIFICATION_EXIT_TREE: { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_HELP)) { + DisplayServer::get_singleton()->help_set_search_callbacks(); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { tree_cache.clear(); diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h index 18f79b824f..1982da04e8 100644 --- a/editor/editor_help_search.h +++ b/editor/editor_help_search.h @@ -84,6 +84,15 @@ class EditorHelpSearch : public ConfirmationDialog { void _filter_combo_item_selected(int p_option); void _confirmed(); + bool _all_terms_in_name(const Vector<String> &p_terms, const String &p_name) const; + void _match_method_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::MethodDoc> &p_methods, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const; + void _match_const_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ConstantDoc> &p_constants, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const; + void _match_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::PropertyDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const; + void _match_theme_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ThemeItemDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const; + + Dictionary _native_search_cb(const String &p_search_string, int p_result_limit); + void _native_action_cb(const String &p_item_string); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 0f7a121a02..622822df09 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3533,6 +3533,9 @@ void EditorInspector::edit(Object *p_object) { object = p_object; + property_configuration_warnings.clear(); + _update_configuration_warnings(); + if (object) { update_scroll_request = 0; //reset if (scroll_cache.has(object->get_instance_id())) { //if exists, set something else @@ -4056,8 +4059,48 @@ void EditorInspector::_node_removed(Node *p_node) { void EditorInspector::_warning_changed(Node *p_node) { if (p_node == object) { - update_tree_pending = true; + // Only update the tree if the list of configuration warnings has changed. + if (_update_configuration_warnings()) { + update_tree_pending = true; + } + } +} + +bool EditorInspector::_update_configuration_warnings() { + Node *node = Object::cast_to<Node>(object); + if (!node) { + return false; } + + bool changed = false; + LocalVector<int> found_warning_indices; + + // New and changed warnings. + Vector<Dictionary> warnings = node->get_configuration_warnings_as_dicts(); + for (const Dictionary &warning : warnings) { + if (!warning.has("property")) { + continue; + } + + int found_warning_index = property_configuration_warnings.find(warning); + if (found_warning_index < 0) { + found_warning_index = property_configuration_warnings.size(); + property_configuration_warnings.push_back(warning); + changed = true; + } + found_warning_indices.push_back(found_warning_index); + } + + // Removed warnings. + for (uint32_t i = 0; i < property_configuration_warnings.size(); i++) { + if (found_warning_indices.find(i) < 0) { + property_configuration_warnings.remove_at(i); + i--; + changed = true; + } + } + + return changed; } void EditorInspector::_notification(int p_what) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 2066002a03..4e937ae4ad 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -514,6 +514,7 @@ class EditorInspector : public ScrollContainer { int property_focusable; int update_scroll_request; + LocalVector<Dictionary> property_configuration_warnings; HashMap<StringName, HashMap<StringName, String>> doc_path_cache; HashSet<StringName> restart_request_props; HashMap<String, String> custom_property_descriptions; @@ -543,6 +544,7 @@ class EditorInspector : public ScrollContainer { void _node_removed(Node *p_node); void _warning_changed(Node *p_node); + bool _update_configuration_warnings(); HashMap<StringName, int> per_array_page; void _page_change_request(int p_new_page, const StringName &p_array_prefix); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 89ee57e190..827e2c7bf6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2250,21 +2250,15 @@ void EditorNode::push_item(Object *p_object, const String &p_property, bool p_in hide_unused_editors(); return; } - - ObjectID id = p_object->get_instance_id(); - if (id != editor_history.get_current()) { - if (p_inspector_only) { - editor_history.add_object(id, String(), true); - } else if (p_property.is_empty()) { - editor_history.add_object(id); - } else { - editor_history.add_object(id, p_property); - } - } - + _add_to_history(p_object, p_property, p_inspector_only); _edit_current(); } +void EditorNode::push_item_no_inspector(Object *p_object) { + _add_to_history(p_object, "", false); + _edit_current(false, true); +} + void EditorNode::save_default_environment() { Ref<Environment> fallback = get_tree()->get_root()->get_world_3d()->get_fallback_environment(); @@ -2328,7 +2322,20 @@ static bool overrides_external_editor(Object *p_object) { return script->get_language()->overrides_external_editor(); } -void EditorNode::_edit_current(bool p_skip_foreign) { +void EditorNode::_add_to_history(const Object *p_object, const String &p_property, bool p_inspector_only) { + ObjectID id = p_object->get_instance_id(); + if (id != editor_history.get_current()) { + if (p_inspector_only) { + editor_history.add_object(id, String(), true); + } else if (p_property.is_empty()) { + editor_history.add_object(id); + } else { + editor_history.add_object(id, p_property); + } + } +} + +void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update) { ObjectID current_id = editor_history.get_current(); Object *current_obj = current_id.is_valid() ? ObjectDB::get_instance(current_id) : nullptr; @@ -2377,11 +2384,13 @@ void EditorNode::_edit_current(bool p_skip_foreign) { Resource *current_res = Object::cast_to<Resource>(current_obj); ERR_FAIL_NULL(current_res); - InspectorDock::get_inspector_singleton()->edit(current_res); - SceneTreeDock::get_singleton()->set_selected(nullptr); - NodeDock::get_singleton()->set_node(nullptr); - InspectorDock::get_singleton()->update(nullptr); - ImportDock::get_singleton()->set_edit_path(current_res->get_path()); + if (!p_skip_inspector_update) { + InspectorDock::get_inspector_singleton()->edit(current_res); + SceneTreeDock::get_singleton()->set_selected(nullptr); + NodeDock::get_singleton()->set_node(nullptr); + InspectorDock::get_singleton()->update(nullptr); + ImportDock::get_singleton()->set_edit_path(current_res->get_path()); + } int subr_idx = current_res->get_path().find("::"); if (subr_idx != -1) { @@ -5624,8 +5633,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins if (edited_scene_map.size() > 0) { // Reload the new instance. Error err; - Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); - instance_scene_packed_scene->set_path(p_instance_path, true); + Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); ERR_FAIL_COND(err != OK); ERR_FAIL_COND(instance_scene_packed_scene.is_null()); @@ -5732,8 +5740,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // be properly updated. for (String path : required_load_paths) { if (!local_scene_cache.find(path)) { - current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); - current_packed_scene->set_path(path, true); + current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); local_scene_cache[path] = current_packed_scene; } else { current_packed_scene = local_scene_cache[path]; diff --git a/editor/editor_node.h b/editor/editor_node.h index f1f75ddd84..0f6e031424 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -518,7 +518,8 @@ private: void _dialog_action(String p_file); - void _edit_current(bool p_skip_foreign = false); + void _add_to_history(const Object *p_object, const String &p_property, bool p_inspector_only); + void _edit_current(bool p_skip_foreign = false, bool p_skip_inspector_update = false); void _dialog_display_save_error(String p_file, Error p_error); void _dialog_display_load_error(String p_file, Error p_error); @@ -752,6 +753,7 @@ public: void show_about() { _menu_option_confirm(HELP_ABOUT, false); } void push_item(Object *p_object, const String &p_property = "", bool p_inspector_only = false); + void push_item_no_inspector(Object *p_object); void edit_item(Object *p_object, Object *p_editing_owner); void push_node_item(Node *p_node); void hide_unused_editors(const Object *p_editing_owner = nullptr); diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 33395c8712..d5135f4198 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -233,6 +233,7 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { for (int i = 0; i < instance_count; i++) { List<String> instance_args(args); RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args); + RunInstancesDialog::get_singleton()->apply_custom_features(i); if (OS::get_singleton()->is_stdout_verbose()) { print_line(vformat("Running: %s", exec)); diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp index afa27435cd..5f42bb828f 100644 --- a/editor/import/resource_importer_imagefont.cpp +++ b/editor/import/resource_importer_imagefont.cpp @@ -61,10 +61,13 @@ bool ResourceImporterImageFont::get_option_visibility(const String &p_path, cons void ResourceImporterImageFont::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const { r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "character_ranges"), Vector<String>())); + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "kerning_pairs"), Vector<String>())); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "columns"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rows"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "image_margin"), Rect2i())); r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "character_margin"), Rect2i())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "ascent"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "descent"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("Font")), Array())); @@ -72,30 +75,15 @@ void ResourceImporterImageFont::get_import_options(const String &p_path, List<Im r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "scaling_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled (Integer),Enabled (Fractional)"), TextServer::FIXED_SIZE_SCALE_ENABLED)); } -bool ResourceImporterImageFont::_decode_range(const String &p_token, int32_t &r_pos) { - if (p_token.begins_with("U+") || p_token.begins_with("u+") || p_token.begins_with("0x")) { - // Unicode character hex index. - r_pos = p_token.substr(2).hex_to_int(); - return true; - } else if (p_token.length() == 3 && p_token[0] == '\'' && p_token[2] == '\'') { - // Unicode character. - r_pos = p_token.unicode_at(1); - return true; - } else if (p_token.is_numeric()) { - // Unicode character decimal index. - r_pos = p_token.to_int(); - return true; - } else { - return false; - } -} - Error ResourceImporterImageFont::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { print_verbose("Importing image font from: " + p_source_file); int columns = p_options["columns"]; int rows = p_options["rows"]; + int ascent = p_options["ascent"]; + int descent = p_options["descent"]; Vector<String> ranges = p_options["character_ranges"]; + Vector<String> kern = p_options["kerning_pairs"]; Array fallbacks = p_options["fallbacks"]; Rect2i img_margin = p_options["image_margin"]; Rect2i char_margin = p_options["character_margin"]; @@ -130,39 +118,186 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin font->set_texture_image(0, Vector2i(chr_height, 0), 0, img); font->set_fixed_size_scale_mode(smode); - int pos = 0; - for (int i = 0; i < ranges.size(); i++) { - int32_t start, end; - Vector<String> tokens = ranges[i].split("-"); - if (tokens.size() == 2) { - if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) { - WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); - continue; + int32_t pos = 0; + for (const String &range : ranges) { + int32_t start = -1; + int32_t end = -1; + int chr_adv = 0; + Vector2i chr_off; + + { + enum RangeParseStep { + STEP_START_BEGIN, + STEP_START_READ_HEX, + STEP_START_READ_DEC, + STEP_END_BEGIN, + STEP_END_READ_HEX, + STEP_END_READ_DEC, + STEP_ADVANCE_BEGIN, + STEP_OFF_X_BEGIN, + STEP_OFF_Y_BEGIN, + STEP_FINISHED, + }; + RangeParseStep step = STEP_START_BEGIN; + String token; + for (int c = 0; c < range.length(); c++) { + switch (step) { + case STEP_START_BEGIN: + case STEP_END_BEGIN: { + // Read range start/end first symbol. + if (range[c] == 'U' || range[c] == 'u') { + if ((c <= range.length() - 2) && range[c + 1] == '+') { + token = String(); + if (step == STEP_START_BEGIN) { + step = STEP_START_READ_HEX; + } else { + step = STEP_END_READ_HEX; + } + c++; // Skip "+". + continue; + } + } else if (range[c] == '0') { + if ((c <= range.length() - 2) && range[c + 1] == 'x') { + token = String(); + if (step == STEP_START_BEGIN) { + step = STEP_START_READ_HEX; + } else { + step = STEP_END_READ_HEX; + } + c++; // Skip "x". + continue; + } + } else if (range[c] == '\'' || range[c] == '\"') { + if ((c <= range.length() - 3) && (range[c + 2] == '\'' || range[c + 2] == '\"')) { + token = String(); + if (step == STEP_START_BEGIN) { + start = range.unicode_at(c + 1); + step = STEP_END_BEGIN; + } else { + end = range.unicode_at(c + 1); + step = STEP_ADVANCE_BEGIN; + } + c = c + 2; // Skip the rest or token. + continue; + } + } else if (is_digit(range[c])) { + // Read decimal value, start. + c++; + token = String(); + if (step == STEP_START_BEGIN) { + step = STEP_START_READ_DEC; + } else { + step = STEP_END_READ_DEC; + } + token += range[c]; + continue; + } + [[fallthrough]]; + } + case STEP_ADVANCE_BEGIN: + case STEP_OFF_X_BEGIN: + case STEP_OFF_Y_BEGIN: { + // Read advance and offset. + if (range[c] == ' ') { + int next = range.find(" ", c + 1); + if (next < c) { + next = range.length(); + } + if (step == STEP_OFF_X_BEGIN) { + chr_off.x = range.substr(c + 1, next - (c + 1)).to_int(); + step = STEP_OFF_Y_BEGIN; + } else if (step == STEP_OFF_Y_BEGIN) { + chr_off.y = range.substr(c + 1, next - (c + 1)).to_int(); + step = STEP_FINISHED; + } else { + chr_adv = range.substr(c + 1, next - (c + 1)).to_int(); + step = STEP_OFF_X_BEGIN; + } + c = next - 1; + continue; + } + } break; + case STEP_START_READ_HEX: + case STEP_END_READ_HEX: { + // Read hexadecimal value. + if (is_hex_digit(range[c])) { + token += range[c]; + } else { + if (step == STEP_START_READ_HEX) { + start = token.hex_to_int(); + step = STEP_END_BEGIN; + } else { + end = token.hex_to_int(); + step = STEP_ADVANCE_BEGIN; + } + } + } break; + case STEP_START_READ_DEC: + case STEP_END_READ_DEC: { + // Read decimal value. + if (is_digit(range[c])) { + token += range[c]; + } else { + if (step == STEP_START_READ_DEC) { + start = token.to_int(); + step = STEP_END_BEGIN; + } else { + end = token.to_int(); + step = STEP_ADVANCE_BEGIN; + } + } + } break; + default: { + WARN_PRINT(vformat("Invalid character \"%d\" in the range: \"%s\"", c, range)); + } break; + } + } + if (end == -1) { + end = start; } - } else if (tokens.size() == 1) { - if (!_decode_range(tokens[0], start)) { - WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + if (start == -1) { + WARN_PRINT(vformat("Invalid range: \"%s\"", range)); continue; } - end = start; - } else { - WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); - continue; } - for (int32_t idx = start; idx <= end; idx++) { + + for (int32_t idx = MIN(start, end); idx <= MAX(start, end); idx++) { ERR_FAIL_COND_V_MSG(pos >= count, ERR_CANT_CREATE, "Too many characters in range, should be " + itos(columns * rows)); int x = pos % columns; int y = pos / columns; - font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width, 0)); - font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height)); + font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width + chr_adv, 0)); + font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height) + chr_off); font->set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height)); font->set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height)); font->set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0); pos++; } } - font->set_cache_ascent(0, chr_height, 0.5 * chr_height); - font->set_cache_descent(0, chr_height, 0.5 * chr_height); + for (const String &kp : kern) { + const Vector<String> &kp_tokens = kp.split(" "); + if (kp_tokens.size() != 3) { + WARN_PRINT(vformat("Invalid kerning pairs string: \"%s\"", kp)); + continue; + } + int offset = kp_tokens[2].to_int(); + for (int a = 0; a < kp_tokens[0].length(); a++) { + for (int b = 0; b < kp_tokens[1].length(); b++) { + font->set_kerning(0, chr_height, Vector2i(kp_tokens[0].unicode_at(a), kp_tokens[1].unicode_at(b)), Vector2(offset, 0)); + } + } + } + + if (ascent > 0) { + font->set_cache_ascent(0, chr_height, ascent); + } else { + font->set_cache_ascent(0, chr_height, 0.5 * chr_height); + } + + if (descent > 0) { + font->set_cache_descent(0, chr_height, descent); + } else { + font->set_cache_descent(0, chr_height, 0.5 * chr_height); + } int flg = 0; if ((bool)p_options["compress"]) { diff --git a/editor/import/resource_importer_imagefont.h b/editor/import/resource_importer_imagefont.h index 1f6d1bf778..065351c361 100644 --- a/editor/import/resource_importer_imagefont.h +++ b/editor/import/resource_importer_imagefont.h @@ -39,8 +39,6 @@ class ResourceImporterImageFont : public ResourceImporter { GDCLASS(ResourceImporterImageFont, ResourceImporter); public: - static bool _decode_range(const String &p_token, int32_t &r_pos); - virtual String get_importer_name() const override; virtual String get_visible_name() const override; virtual void get_recognized_extensions(List<String> *p_extensions) const override; diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 8f0198c2c0..3a96d3e8c3 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -96,7 +96,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { // Multi-instance, start/stop. debug_menu->add_separator(); - debug_menu->add_item(TTR("Run Multiple Instances..."), RUN_MULTIPLE_INSTANCES); + debug_menu->add_item(TTR("Customize Run Instances..."), RUN_MULTIPLE_INSTANCES); debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); run_instances_dialog = memnew(RunInstancesDialog); @@ -186,7 +186,7 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { } break; case RUN_MULTIPLE_INSTANCES: { - run_instances_dialog->popup_centered(); + run_instances_dialog->popup_dialog(); } break; } diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 1551fabc0c..bc19735c4f 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -2363,7 +2363,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { tiles_bottom_panel->set_name(TTR("Tiles")); missing_source_label = memnew(Label); - missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Go to the TileSet bottom tab to add one.")); + missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Go to the TileSet bottom panel to add one.")); missing_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); missing_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); missing_source_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); diff --git a/editor/run_instances_dialog.cpp b/editor/run_instances_dialog.cpp index 0004b833f2..e446af0808 100644 --- a/editor/run_instances_dialog.cpp +++ b/editor/run_instances_dialog.cpp @@ -36,11 +36,11 @@ #include "scene/gui/check_box.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/separator.h" #include "scene/gui/spin_box.h" +#include "scene/gui/tree.h" #include "scene/main/timer.h" -RunInstancesDialog *RunInstancesDialog::singleton = nullptr; - void RunInstancesDialog::_fetch_main_args() { if (!main_args_edit->has_focus()) { // Only set the text if the user is not currently editing it. main_args_edit->set_text(GLOBAL_GET("editor/run/main_run_args")); @@ -56,68 +56,66 @@ void RunInstancesDialog::_start_instance_timer() { } void RunInstancesDialog::_refresh_argument_count() { - while (argument_container->get_child_count() > 0) { - memdelete(argument_container->get_child(0)); + instance_tree->clear(); + instance_tree->create_item(); // Root. + + while (instance_count->get_value() > stored_data.size()) { + stored_data.append(Dictionary()); } + stored_data.resize(instance_count->get_value()); + instances_data.resize(stored_data.size()); + InstanceData *instances_write = instances_data.ptrw(); - override_list.resize(instance_count->get_value()); - argument_list.resize_zeroed(instance_count->get_value()); + for (int i = 0; i < instances_data.size(); i++) { + InstanceData instance; + const Dictionary &instance_data = stored_data[i]; - for (int i = 0; i < argument_list.size(); i++) { - VBoxContainer *instance_vb = memnew(VBoxContainer); - argument_container->add_child(instance_vb); + _create_instance(instance, instance_data, i + 1); + instances_write[i] = instance; + } +} - HBoxContainer *hbox = memnew(HBoxContainer); - instance_vb->add_child(hbox); +void RunInstancesDialog::_create_instance(InstanceData &p_instance, const Dictionary &p_data, int p_idx) { + TreeItem *instance_item = instance_tree->create_item(); + p_instance.item = instance_item; - Label *l = memnew(Label); - hbox->add_child(l); - l->set_text(vformat(TTR("Instance %d"), i + 1)); - - CheckBox *cb = memnew(CheckBox); - hbox->add_child(cb); - cb->set_text(TTR("Override Main Run Args")); - cb->set_tooltip_text(TTR("If disabled, the instance arguments will be appended after the Main Run Args.")); - cb->set_pressed(override_list[i]); - cb->set_h_size_flags(Control::SIZE_SHRINK_END | Control::SIZE_EXPAND); - cb->connect(SNAME("toggled"), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); - instance_vb->set_meta(SNAME("override"), cb); - - LineEdit *le = memnew(LineEdit); - instance_vb->add_child(le); - le->set_text(argument_list[i]); - le->connect(SNAME("text_changed"), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); - instance_vb->set_meta(SNAME("args"), le); - } + instance_item->set_cell_mode(COLUMN_OVERRIDE_ARGS, TreeItem::CELL_MODE_CHECK); + instance_item->set_editable(COLUMN_OVERRIDE_ARGS, true); + instance_item->set_text(COLUMN_OVERRIDE_ARGS, TTR("Enabled")); + instance_item->set_checked(COLUMN_OVERRIDE_ARGS, p_data.get("override_args", false)); + + instance_item->set_editable(COLUMN_LAUNCH_ARGUMENTS, true); + instance_item->set_text(COLUMN_LAUNCH_ARGUMENTS, p_data.get("arguments", String())); + + instance_item->set_cell_mode(COLUMN_OVERRIDE_FEATURES, TreeItem::CELL_MODE_CHECK); + instance_item->set_editable(COLUMN_OVERRIDE_FEATURES, true); + instance_item->set_text(COLUMN_OVERRIDE_FEATURES, TTR("Enabled")); + instance_item->set_checked(COLUMN_OVERRIDE_FEATURES, p_data.get("override_features", false)); + + instance_item->set_editable(COLUMN_FEATURE_TAGS, true); + instance_item->set_text(COLUMN_FEATURE_TAGS, p_data.get("features", String())); } void RunInstancesDialog::_save_main_args() { ProjectSettings::get_singleton()->set_setting("editor/run/main_run_args", main_args_edit->get_text()); ProjectSettings::get_singleton()->save(); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_main_feature_tags", main_features_edit->get_text()); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_enabled", enable_multiple_instances_checkbox->is_pressed()); } void RunInstancesDialog::_save_arguments() { - override_list.clear(); - override_list.resize(argument_container->get_child_count()); - argument_list.clear(); - argument_list.resize(argument_container->get_child_count()); - - String *w = argument_list.ptrw(); - for (int i = 0; i < argument_container->get_child_count(); i++) { - const Node *instance_vb = argument_container->get_child(i); - - CheckBox *check_box = Object::cast_to<CheckBox>(instance_vb->get_meta(SNAME("override"))); - ERR_FAIL_NULL(check_box); - override_list[i] = check_box->is_pressed(); - - LineEdit *edit = Object::cast_to<LineEdit>(instance_vb->get_meta(SNAME("args"))); - ERR_FAIL_NULL(edit); - w[i] = edit->get_text(); + stored_data.resize(instances_data.size()); + + for (int i = 0; i < instances_data.size(); i++) { + const InstanceData &instance = instances_data[i]; + Dictionary dict; + dict["override_args"] = instance.overrides_run_args(); + dict["arguments"] = instance.get_launch_arguments(); + dict["override_features"] = instance.overrides_features(); + dict["features"] = instance.get_feature_tags(); + stored_data[i] = dict; } - - EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_enabled", enable_multiple_instances_checkbox->is_pressed()); - EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_overrides", override_list); - EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_arguments", argument_list); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_instances_config", stored_data); } Vector<String> RunInstancesDialog::_split_cmdline_args(const String &p_arg_string) const { @@ -156,6 +154,10 @@ Vector<String> RunInstancesDialog::_split_cmdline_args(const String &p_arg_strin return split_args; } +void RunInstancesDialog::popup_dialog() { + popup_centered(Vector2i(1200, 600) * EDSCALE); +} + int RunInstancesDialog::get_instance_count() const { if (enable_multiple_instances_checkbox->is_pressed()) { return instance_count->get_value(); @@ -165,12 +167,16 @@ int RunInstancesDialog::get_instance_count() const { } void RunInstancesDialog::get_argument_list_for_instance(int p_idx, List<String> &r_list) const { - bool override_args = override_list[p_idx]; + bool override_args = instances_data[p_idx].overrides_run_args(); bool use_multiple_instances = enable_multiple_instances_checkbox->is_pressed(); String raw_custom_args; - if (use_multiple_instances && override_args) { - raw_custom_args = argument_list[p_idx]; + if (use_multiple_instances) { + if (override_args) { + raw_custom_args = instances_data[p_idx].get_launch_arguments(); + } else { + raw_custom_args = main_args_edit->get_text() + " " + instances_data[p_idx].get_launch_arguments(); + } } else { raw_custom_args = main_args_edit->get_text(); } @@ -218,15 +224,37 @@ void RunInstancesDialog::get_argument_list_for_instance(int p_idx, List<String> } } } +} + +void RunInstancesDialog::apply_custom_features(int p_instance_idx) { + const InstanceData &instance = instances_data[p_instance_idx]; + + String raw_text; + if (enable_multiple_instances_checkbox->is_pressed()) { + if (instance.overrides_features()) { + raw_text = instance.get_feature_tags(); + } else { + raw_text = main_features_edit->get_text() + "," + instance.get_feature_tags(); + } + } else { + raw_text = main_features_edit->get_text(); + } + + const Vector<String> raw_list = raw_text.split(","); + Vector<String> stripped_features; - if (use_multiple_instances && !override_args) { - r_list.push_back(argument_list[p_idx]); + for (int i = 0; i < raw_list.size(); i++) { + String f = raw_list[i].strip_edges(); + if (!f.is_empty()) { + stripped_features.push_back(f); + } } + OS::get_singleton()->set_environment("GODOT_EDITOR_CUSTOM_FEATURES", String(",").join(stripped_features)); } RunInstancesDialog::RunInstancesDialog() { singleton = this; - set_min_size(Vector2i(0, 600 * EDSCALE)); + set_title(TTR("Run Instances")); main_apply_timer = memnew(Timer); main_apply_timer->set_wait_time(0.5); @@ -255,45 +283,78 @@ RunInstancesDialog::RunInstancesDialog() { ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &RunInstancesDialog::_fetch_main_args)); main_args_edit->connect("text_changed", callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1)); + { + Label *l = memnew(Label); + main_vb->add_child(l); + l->set_text(TTR("Main Feature Tags:")); + } + + main_features_edit = memnew(LineEdit); + main_features_edit->set_text(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_main_feature_tags", "")); + main_vb->add_child(main_features_edit); + main_features_edit->connect("text_changed", callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1)); + + { + HSeparator *sep = memnew(HSeparator); + main_vb->add_child(sep); + } + enable_multiple_instances_checkbox = memnew(CheckBox); enable_multiple_instances_checkbox->set_text(TTR("Enable Multiple Instances")); enable_multiple_instances_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_enabled", false)); main_vb->add_child(enable_multiple_instances_checkbox); - enable_multiple_instances_checkbox->connect("pressed", callable_mp(this, &RunInstancesDialog::_start_instance_timer)); + enable_multiple_instances_checkbox->connect("pressed", callable_mp(this, &RunInstancesDialog::_start_main_timer)); - override_list = EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_overrides", varray(false, false, false, false)); - argument_list = EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_arguments", PackedStringArray{ "", "", "", "" }); + stored_data = TypedArray<Dictionary>(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_instances_config", TypedArray<Dictionary>())); instance_count = memnew(SpinBox); instance_count->set_min(1); instance_count->set_max(20); - instance_count->set_value(argument_list.size()); + instance_count->set_value(stored_data.size()); main_vb->add_child(instance_count); instance_count->connect("value_changed", callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); instance_count->connect("value_changed", callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1)); enable_multiple_instances_checkbox->connect("toggled", callable_mp(instance_count, &SpinBox::set_editable)); + instance_count->set_editable(enable_multiple_instances_checkbox->is_pressed()); { Label *l = memnew(Label); - l->set_text(TTR("Launch Arguments")); + l->set_text(TTR("Instance Configuration")); l->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + l->set_theme_type_variation("HeaderSmall"); main_vb->add_child(l); } - { - ScrollContainer *arguments_scroll = memnew(ScrollContainer); - arguments_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - arguments_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL); - arguments_scroll->set_v_size_flags(Control::SIZE_EXPAND_FILL); - main_vb->add_child(arguments_scroll); - - argument_container = memnew(VBoxContainer); - argument_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); - arguments_scroll->add_child(argument_container); - } + instance_tree = memnew(Tree); + instance_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + instance_tree->set_h_scroll_enabled(false); + instance_tree->set_columns(4); + instance_tree->set_column_titles_visible(true); + instance_tree->set_column_title(COLUMN_OVERRIDE_ARGS, TTR("Override Main Run Args")); + instance_tree->set_column_expand(COLUMN_OVERRIDE_ARGS, false); + instance_tree->set_column_title(COLUMN_LAUNCH_ARGUMENTS, TTR("Launch Arguments")); + instance_tree->set_column_title(COLUMN_OVERRIDE_FEATURES, TTR("Override Main Tags")); + instance_tree->set_column_expand(COLUMN_OVERRIDE_FEATURES, false); + instance_tree->set_column_title(COLUMN_FEATURE_TAGS, TTR("Feature Tags")); + instance_tree->set_hide_root(true); + main_vb->add_child(instance_tree); _refresh_argument_count(); + instance_tree->connect("item_edited", callable_mp(this, &RunInstancesDialog::_start_instance_timer)); +} + +bool RunInstancesDialog::InstanceData::overrides_run_args() const { + return item->is_checked(COLUMN_OVERRIDE_ARGS); +} + +String RunInstancesDialog::InstanceData::get_launch_arguments() const { + return item->get_text(COLUMN_LAUNCH_ARGUMENTS); +} + +bool RunInstancesDialog::InstanceData::overrides_features() const { + return item->is_checked(COLUMN_OVERRIDE_FEATURES); +} - set_title(TTR("Run Multiple Instances")); - set_min_size(Size2i(400 * EDSCALE, 0)); +String RunInstancesDialog::InstanceData::get_feature_tags() const { + return item->get_text(COLUMN_FEATURE_TAGS); } diff --git a/editor/run_instances_dialog.h b/editor/run_instances_dialog.h index cc4cd6eb5b..8e0007dc8e 100644 --- a/editor/run_instances_dialog.h +++ b/editor/run_instances_dialog.h @@ -37,24 +37,41 @@ class CheckBox; class LineEdit; class SpinBox; class Timer; -class VBoxContainer; +class Tree; +class TreeItem; class RunInstancesDialog : public AcceptDialog { GDCLASS(RunInstancesDialog, AcceptDialog); -private: - static RunInstancesDialog *singleton; + enum Columns { + COLUMN_OVERRIDE_ARGS, + COLUMN_LAUNCH_ARGUMENTS, + COLUMN_OVERRIDE_FEATURES, + COLUMN_FEATURE_TAGS, + }; + + struct InstanceData { + TreeItem *item = nullptr; + + bool overrides_run_args() const; + String get_launch_arguments() const; + bool overrides_features() const; + String get_feature_tags() const; + }; + + inline static RunInstancesDialog *singleton = nullptr; + + TypedArray<Dictionary> stored_data; + Vector<InstanceData> instances_data; Timer *main_apply_timer = nullptr; Timer *instance_apply_timer = nullptr; LineEdit *main_args_edit = nullptr; + LineEdit *main_features_edit = nullptr; SpinBox *instance_count = nullptr; CheckBox *enable_multiple_instances_checkbox = nullptr; - VBoxContainer *argument_container = nullptr; - - Array override_list; - PackedStringArray argument_list; + Tree *instance_tree = nullptr; void _fetch_main_args(); // These 2 methods are necessary due to callable_mp() not supporting default arguments. @@ -62,14 +79,17 @@ private: void _start_instance_timer(); void _refresh_argument_count(); + void _create_instance(InstanceData &p_instance, const Dictionary &p_data, int p_idx); void _save_main_args(); void _save_arguments(); // Separates command line arguments without splitting up quoted strings. Vector<String> _split_cmdline_args(const String &p_arg_string) const; public: + void popup_dialog(); int get_instance_count() const; void get_argument_list_for_instance(int p_idx, List<String> &r_list) const; + void apply_custom_features(int p_instance_idx); static RunInstancesDialog *get_singleton() { return singleton; } RunInstancesDialog(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index d7738e0ba5..413281ad08 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1578,9 +1578,7 @@ void SceneTreeDock::_load_request(const String &p_path) { } void SceneTreeDock::_script_open_request(const Ref<Script> &p_script) { - if (ScriptEditor::get_singleton()->edit(p_script, true)) { - EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT); - } + EditorNode::get_singleton()->push_item_no_inspector(p_script.ptr()); } void SceneTreeDock::_push_item(Object *p_object) { diff --git a/main/main.cpp b/main/main.cpp index dd0ebdd154..5e54a52f61 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2879,7 +2879,7 @@ Error Main::setup2() { GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); - + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); OS::get_singleton()->benchmark_end_measure("Startup", "Setup Window and Boot"); } diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml index 1bc3a1a3fc..2f1f472a7e 100644 --- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml +++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml @@ -78,7 +78,7 @@ <method name="is_environment_blend_mode_alpha_supported"> <return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" /> <description> - Returns [enum OpenXRAPIExtension.OpenXRAlphaBlendModeSupport] denoting if [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really support, emulated or not supported at all. + Returns [enum OpenXRAPIExtension.OpenXRAlphaBlendModeSupport] denoting if [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really supported, emulated or not supported at all. </description> </method> <method name="is_initialized"> diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index b06164246e..01ecbc7164 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -127,6 +127,16 @@ bool DisplayServerAndroid::is_dark_mode() const { return godot_java->is_dark_mode(); } +void DisplayServerAndroid::set_system_theme_change_callback(const Callable &p_callable) { + system_theme_changed = p_callable; +} + +void DisplayServerAndroid::emit_system_theme_changed() { + if (system_theme_changed.is_valid()) { + system_theme_changed.call_deferred(); + } +} + void DisplayServerAndroid::clipboard_set(const String &p_text) { GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); ERR_FAIL_NULL(godot_java); diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index b425b2d9ae..c95eaddf93 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -84,6 +84,8 @@ class DisplayServerAndroid : public DisplayServer { Callable input_text_callback; Callable rect_changed_callback; + Callable system_theme_changed; + void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const; static void _dispatch_input_events(const Ref<InputEvent> &p_event); @@ -103,8 +105,11 @@ public: virtual void tts_resume() override; virtual void tts_stop() override; + void emit_system_theme_changed(); + virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; virtual void clipboard_set(const String &p_text) override; virtual String clipboard_get() const override; 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 da86e67c7d..a0e020b55e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -149,6 +149,7 @@ class Godot(private val context: Context) : SensorEventListener { private var useApkExpansion = false private var useImmersive = false private var useDebugOpengl = false + private var darkMode = false; private var containerLayout: FrameLayout? = null var renderView: GodotRenderView? = null @@ -184,6 +185,8 @@ class Godot(private val context: Context) : SensorEventListener { return } + darkMode = context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + beginBenchmarkMeasure("Startup", "Godot::onCreate") try { this.primaryHost = primaryHost @@ -560,6 +563,17 @@ class Godot(private val context: Context) : SensorEventListener { } /** + * Configuration change callback + */ + fun onConfigurationChanged(newConfig: Configuration) { + var newDarkMode = newConfig.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + if (darkMode != newDarkMode) { + darkMode = newDarkMode + GodotLib.onNightModeChanged() + } + } + + /** * Activity result callback */ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -596,11 +610,13 @@ class Godot(private val context: Context) : SensorEventListener { // These properties are defined after Godot setup completion, so we retrieve them here. val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) + val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")); runOnUiThread { renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) + setRotaryInputAxis(rotaryInputAxis) } } @@ -731,7 +747,7 @@ class Godot(private val context: Context) : SensorEventListener { */ @Keep private fun isDarkModeSupported(): Boolean { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + return context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) != Configuration.UI_MODE_NIGHT_UNDEFINED } /** @@ -739,10 +755,7 @@ class Godot(private val context: Context) : SensorEventListener { */ @Keep private fun isDarkMode(): Boolean { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES - } - return false + return darkMode } fun hasClipboard(): Boolean { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index 643c9a658e..a323045e1b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -38,6 +38,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Messenger; @@ -147,6 +148,13 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH @CallSuper @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + godot.onConfigurationChanged(newConfig); + } + + @CallSuper + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCallback != null) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index fee50e93c2..d0c3d4a687 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -220,6 +220,11 @@ public class GodotLib { public static native void requestPermissionResult(String p_permission, boolean p_result); /** + * Invoked on the theme light/dark mode change. + */ + public static native void onNightModeChanged(); + + /** * Invoked on the GL thread to configure the height of the virtual keyboard. */ public static native void setVirtualKeyboardHeight(int p_height); diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java index 3070a8a207..dc8a0e54bb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java @@ -34,10 +34,13 @@ import org.godotengine.godot.*; import android.content.Context; import android.content.res.Configuration; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.text.InputFilter; import android.text.InputType; +import android.text.TextUtils; +import android.text.method.DigitsKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.inputmethod.EditorInfo; @@ -45,6 +48,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import java.lang.ref.WeakReference; +import java.util.Locale; public class GodotEditText extends EditText { // =========================================================== @@ -137,6 +141,7 @@ public class GodotEditText extends EditText { } int inputType = InputType.TYPE_CLASS_TEXT; + String acceptCharacters = null; switch (edit.getKeyboardType()) { case KEYBOARD_TYPE_DEFAULT: inputType = InputType.TYPE_CLASS_TEXT; @@ -148,7 +153,8 @@ public class GodotEditText extends EditText { inputType = InputType.TYPE_CLASS_NUMBER; break; case KEYBOARD_TYPE_NUMBER_DECIMAL: - inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED; + inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL; + acceptCharacters = "0123456789,.- "; break; case KEYBOARD_TYPE_PHONE: inputType = InputType.TYPE_CLASS_PHONE; @@ -165,6 +171,14 @@ public class GodotEditText extends EditText { } edit.setInputType(inputType); + if (!TextUtils.isEmpty(acceptCharacters)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + edit.setKeyListener(DigitsKeyListener.getInstance(Locale.getDefault())); + } else { + edit.setKeyListener(DigitsKeyListener.getInstance(acceptCharacters)); + } + } + edit.mInputWrapper.setOriginText(text); edit.addTextChangedListener(edit.mInputWrapper); final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 38c115ad7f..fe971cf442 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -57,6 +57,9 @@ import java.util.Set; public class GodotInputHandler implements InputManager.InputDeviceListener { private static final String TAG = GodotInputHandler.class.getSimpleName(); + private static final int ROTARY_INPUT_VERTICAL_AXIS = 1; + private static final int ROTARY_INPUT_HORIZONTAL_AXIS = 0; + private final SparseIntArray mJoystickIds = new SparseIntArray(4); private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4); @@ -71,6 +74,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { */ private int lastSeenToolType = MotionEvent.TOOL_TYPE_UNKNOWN; + private static int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; + public GodotInputHandler(GodotRenderView godotView) { final Context context = godotView.getView().getContext(); mRenderView = godotView; @@ -102,6 +107,13 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { this.godotGestureHandler.setPanningAndScalingEnabled(enable); } + /** + * On Wear OS devices, sets which axis of the mouse wheel rotary input is mapped to. This is 1 (vertical axis) by default. + */ + public void setRotaryInputAxis(int axis) { + rotaryInputAxis = axis; + } + private boolean isKeyEventGameDevice(int source) { // Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD) if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD)) @@ -484,8 +496,22 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { final float tiltX = (float)-Math.sin(orientation) * tiltMult; final float tiltY = (float)Math.cos(orientation) * tiltMult; - final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); - final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + float verticalFactor = 0; + float horizontalFactor = 0; + + // If event came from RotaryEncoder (Bezel or Crown rotate event on Wear OS smart watches), + // convert it to mouse wheel event. + if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) { + if (rotaryInputAxis == ROTARY_INPUT_HORIZONTAL_AXIS) { + horizontalFactor = -event.getAxisValue(MotionEvent.AXIS_SCROLL); + } else { + // If rotaryInputAxis is not ROTARY_INPUT_HORIZONTAL_AXIS then use default ROTARY_INPUT_VERTICAL_AXIS axis. + verticalFactor = -event.getAxisValue(MotionEvent.AXIS_SCROLL); + } + } else { + verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + } boolean sourceMouseRelative = false; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { sourceMouseRelative = event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE); diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 08e792cc04..85d5cf2796 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -487,6 +487,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * Callable(obj, str_method).call_deferredp(argptrs, count); } +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz) { + DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); + if (ds) { + ds->emit_system_theme_changed(); + } +} + JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) { String permission = jstring_to_string(p_permission, env); if (permission == "android.permission.RECORD_AUDIO" && p_result) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 1ddda6c1c8..f32ffc291a 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -66,6 +66,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); } diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 3efd2498d4..6f66783a47 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -77,6 +77,8 @@ class DisplayServerIOS : public DisplayServer { Callable input_event_callback; Callable input_text_callback; + Callable system_theme_changed; + int virtual_keyboard_height = 0; void perform_event(const Ref<InputEvent> &p_event); @@ -109,6 +111,8 @@ public: void send_window_event(DisplayServer::WindowEvent p_event) const; void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + void emit_system_theme_changed(); + // MARK: - Input // MARK: Touches and Apple Pencil @@ -145,6 +149,7 @@ public: virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; virtual Rect2i get_display_safe_area() const override; @@ -159,8 +164,7 @@ public: virtual Vector<DisplayServer::WindowID> get_window_list() const override; - virtual WindowID - get_window_at_screen_position(const Point2i &p_position) const override; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 2895dffdfa..ed69b91fdd 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -387,6 +387,16 @@ bool DisplayServerIOS::is_dark_mode() const { } } +void DisplayServerIOS::set_system_theme_change_callback(const Callable &p_callable) { + system_theme_changed = p_callable; +} + +void DisplayServerIOS::emit_system_theme_changed() { + if (system_theme_changed.is_valid()) { + system_theme_changed.call(); + } +} + Rect2i DisplayServerIOS::get_display_safe_area() const { UIEdgeInsets insets = UIEdgeInsetsZero; UIView *view = AppDelegate.viewController.godotView; diff --git a/platform/ios/godot_view.mm b/platform/ios/godot_view.mm index ff8a4f8921..4b87863fc5 100644 --- a/platform/ios/godot_view.mm +++ b/platform/ios/godot_view.mm @@ -167,6 +167,23 @@ static const float earth_gravity = 9.80665; } } +- (void)system_theme_changed { + DisplayServerIOS *ds = (DisplayServerIOS *)DisplayServer::get_singleton(); + if (ds) { + ds->emit_system_theme_changed(); + } +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + if (@available(iOS 13.0, *)) { + [super traitCollectionDidChange:previousTraitCollection]; + + if ([UITraitCollection currentTraitCollection].userInterfaceStyle != previousTraitCollection.userInterfaceStyle) { + [self system_theme_changed]; + } + } +} + - (void)stopRendering { if (!self.isActive) { return; diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index a3633e72b7..cdebed58b2 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -528,10 +528,10 @@ void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, } } -void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { +void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) { FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud; - while (!portal->file_dialog_thread_abort.is_set()) { + while (!portal->monitor_thread_abort.is_set()) { { MutexLock lock(portal->file_dialog_mutex); for (int i = portal->file_dialogs.size() - 1; i >= 0; i--) { @@ -579,10 +579,44 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { } } } + + if (portal->theme_connection) { + while (true) { + DBusMessage *msg = dbus_connection_pop_message(portal->theme_connection); + if (!msg) { + break; + } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Settings", "SettingChanged")) { + DBusMessageIter iter; + if (dbus_message_iter_init(msg, &iter)) { + const char *value; + dbus_message_iter_get_basic(&iter, &value); + String name_space = String::utf8(value); + dbus_message_iter_next(&iter); + dbus_message_iter_get_basic(&iter, &value); + String key = String::utf8(value); + + if (name_space == "org.freedesktop.appearance" && key == "color-scheme") { + callable_mp(portal, &FreeDesktopPortalDesktop::_system_theme_changed_callback).call_deferred(); + } + } + dbus_message_unref(msg); + break; + } + dbus_message_unref(msg); + } + dbus_connection_read_write(portal->theme_connection, 0); + } + usleep(50000); } } +void FreeDesktopPortalDesktop::_system_theme_changed_callback() { + if (system_theme_changed.is_valid()) { + system_theme_changed.call(); + } +} + FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { #ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED @@ -611,17 +645,34 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { unsupported = true; } + DBusError err; + dbus_error_init(&err); + theme_connection = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + } else { + theme_path = "type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Settings',member='SettingChanged'"; + dbus_bus_add_match(theme_connection, theme_path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + dbus_connection_unref(theme_connection); + theme_connection = nullptr; + } + dbus_connection_read_write(theme_connection, 0); + } + if (!unsupported) { - file_dialog_thread_abort.clear(); - file_dialog_thread.start(FreeDesktopPortalDesktop::_thread_file_dialog_monitor, this); + monitor_thread_abort.clear(); + monitor_thread.start(FreeDesktopPortalDesktop::_thread_monitor, this); } } FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() { - file_dialog_thread_abort.set(); - if (file_dialog_thread.is_started()) { - file_dialog_thread.wait_to_finish(); + monitor_thread_abort.set(); + if (monitor_thread.is_started()) { + monitor_thread.wait_to_finish(); } + for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) { if (fd.connection) { DBusError err; @@ -631,6 +682,13 @@ FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() { dbus_connection_unref(fd.connection); } } + if (theme_connection) { + DBusError err; + dbus_error_init(&err); + dbus_bus_remove_match(theme_connection, theme_path.utf8().get_data(), &err); + dbus_error_free(&err); + dbus_connection_unref(theme_connection); + } } #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h index c9da387241..75afe02a26 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.h +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -34,6 +34,7 @@ #ifdef DBUS_ENABLED #include "core/os/thread.h" +#include "core/os/thread_safe.h" #include "servers/display_server.h" struct DBusMessage; @@ -68,10 +69,15 @@ private: Mutex file_dialog_mutex; Vector<FileDialogData> file_dialogs; - Thread file_dialog_thread; - SafeFlag file_dialog_thread_abort; + Thread monitor_thread; + SafeFlag monitor_thread_abort; - static void _thread_file_dialog_monitor(void *p_ud); + DBusConnection *theme_connection = nullptr; + String theme_path; + Callable system_theme_changed; + void _system_theme_changed_callback(); + + static void _thread_monitor(void *p_ud); public: FreeDesktopPortalDesktop(); @@ -86,6 +92,9 @@ public: // 1: Prefer dark appearance. // 2: Prefer light appearance. uint32_t get_appearance_color_scheme(); + void set_system_theme_change_callback(const Callable &p_system_theme_changed) { + system_theme_changed = p_system_theme_changed; + } }; #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index b8a10ea6b9..85bbfe546a 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -275,6 +275,10 @@ bool DisplayServerWayland::is_dark_mode() const { } } +void DisplayServerWayland::set_system_theme_change_callback(const Callable &p_callable) { + portal_desktop->set_system_theme_change_callback(p_callable); +} + Error DisplayServerWayland::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { WindowID window_id = MAIN_WINDOW_ID; // TODO: Use window IDs for multiwindow support. diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index 58c5dab586..d4da80a55f 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -171,6 +171,7 @@ public: #ifdef DBUS_ENABLED virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index b838e4b870..35bfe81827 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -364,6 +364,10 @@ bool DisplayServerX11::is_dark_mode() const { } } +void DisplayServerX11::set_system_theme_change_callback(const Callable &p_callable) { + portal_desktop->set_system_theme_change_callback(p_callable); +} + Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { WindowID window_id = last_focused_window; diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 7c094d6a41..a5cbe34d26 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -400,6 +400,7 @@ public: #if defined(DBUS_ENABLED) virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 10c8abe663..7373a40237 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -211,12 +211,17 @@ private: IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID; + Callable help_search_callback; + Callable help_action_callback; + struct MenuCall { Variant tag; Callable callback; }; List<MenuCall> deferred_menu_calls; + Callable system_theme_changed; + const NSMenu *_get_menu_root(const String &p_menu_root) const; NSMenu *_get_menu_root(const String &p_menu_root); bool _is_menu_opened(NSMenu *p_menu) const; @@ -251,6 +256,8 @@ public: void menu_open(NSMenu *p_menu); void menu_close(NSMenu *p_menu); + void emit_system_theme_changed(); + bool has_window(WindowID p_window) const; WindowData &get_window(WindowID p_window); @@ -282,6 +289,10 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; + virtual void help_set_search_callbacks(const Callable &p_search_callback = Callable(), const Callable &p_action_callback = Callable()) override; + Callable _help_get_search_callback() const; + Callable _help_get_action_callback() const; + virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override; virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; @@ -351,6 +362,8 @@ public: virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; virtual Color get_accent_color() const override; + virtual Color get_base_color() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 19632dd799..344dc1a8f7 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -840,6 +840,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const { case FEATURE_EXTEND_TO_TITLE: case FEATURE_SCREEN_CAPTURE: case FEATURE_STATUS_INDICATOR: + case FEATURE_NATIVE_HELP: return true; default: { } @@ -851,6 +852,19 @@ String DisplayServerMacOS::get_name() const { return "macOS"; } +void DisplayServerMacOS::help_set_search_callbacks(const Callable &p_search_callback, const Callable &p_action_callback) { + help_search_callback = p_search_callback; + help_action_callback = p_action_callback; +} + +Callable DisplayServerMacOS::_help_get_search_callback() const { + return help_search_callback; +} + +Callable DisplayServerMacOS::_help_get_action_callback() const { + return help_action_callback; +} + bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const { if (submenu_inv.has(p_menu)) { const MenuData &md = submenu[submenu_inv[p_menu]]; @@ -2037,7 +2051,42 @@ bool DisplayServerMacOS::is_dark_mode() const { Color DisplayServerMacOS::get_accent_color() const { if (@available(macOS 10.14, *)) { - NSColor *color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; + __block NSColor *color = nullptr; + if (@available(macOS 11.0, *)) { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; + }]; + } else { + NSAppearance *saved_appearance = [NSAppearance currentAppearance]; + [NSAppearance setCurrentAppearance:[NSApp effectiveAppearance]]; + color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; + [NSAppearance setCurrentAppearance:saved_appearance]; + } + if (color) { + CGFloat components[4]; + [color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; + return Color(components[0], components[1], components[2], components[3]); + } else { + return Color(0, 0, 0, 0); + } + } else { + return Color(0, 0, 0, 0); + } +} + +Color DisplayServerMacOS::get_base_color() const { + if (@available(macOS 10.14, *)) { + __block NSColor *color = nullptr; + if (@available(macOS 11.0, *)) { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + color = [[NSColor controlColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; + }]; + } else { + NSAppearance *saved_appearance = [NSAppearance currentAppearance]; + [NSAppearance setCurrentAppearance:[NSApp effectiveAppearance]]; + color = [[NSColor controlColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; + [NSAppearance setCurrentAppearance:saved_appearance]; + } if (color) { CGFloat components[4]; [color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; @@ -2050,6 +2099,16 @@ Color DisplayServerMacOS::get_accent_color() const { } } +void DisplayServerMacOS::set_system_theme_change_callback(const Callable &p_callable) { + system_theme_changed = p_callable; +} + +void DisplayServerMacOS::emit_system_theme_changed() { + if (system_theme_changed.is_valid()) { + system_theme_changed.call(); + } +} + Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { _THREAD_SAFE_METHOD_ diff --git a/platform/macos/godot_application_delegate.h b/platform/macos/godot_application_delegate.h index 2426fb0b1c..45bd85c45c 100644 --- a/platform/macos/godot_application_delegate.h +++ b/platform/macos/godot_application_delegate.h @@ -36,7 +36,7 @@ #import <AppKit/AppKit.h> #import <Foundation/Foundation.h> -@interface GodotApplicationDelegate : NSObject +@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate> - (void)forceUnbundledWindowActivationHackStep1; - (void)forceUnbundledWindowActivationHackStep2; - (void)forceUnbundledWindowActivationHackStep3; diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm index c1de8ade58..2e76d4aa97 100644 --- a/platform/macos/godot_application_delegate.mm +++ b/platform/macos/godot_application_delegate.mm @@ -39,6 +39,59 @@ return YES; } +- (NSArray<NSString *> *)localizedTitlesForItem:(id)item { + NSArray *item_name = @[ item[1] ]; + return item_name; +} + +- (void)searchForItemsWithSearchString:(NSString *)searchString resultLimit:(NSInteger)resultLimit matchedItemHandler:(void (^)(NSArray *items))handleMatchedItems { + NSMutableArray *found_items = [[NSMutableArray alloc] init]; + + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds && ds->_help_get_search_callback().is_valid()) { + Callable cb = ds->_help_get_search_callback(); + + Variant ret; + Variant search_string = String::utf8([searchString UTF8String]); + Variant result_limit = (uint64_t)resultLimit; + Callable::CallError ce; + const Variant *args[2] = { &search_string, &result_limit }; + + cb.callp(args, 2, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute help search callback: %s."), Variant::get_callable_error_text(cb, args, 2, ce))); + } + Dictionary results = ret; + for (const Variant *E = results.next(); E; E = results.next(E)) { + const String &key = *E; + const String &value = results[*E]; + if (key.length() > 0 && value.length() > 0) { + NSArray *item = @[ [NSString stringWithUTF8String:key.utf8().get_data()], [NSString stringWithUTF8String:value.utf8().get_data()] ]; + [found_items addObject:item]; + } + } + } + + handleMatchedItems(found_items); +} + +- (void)performActionForItem:(id)item { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds && ds->_help_get_action_callback().is_valid()) { + Callable cb = ds->_help_get_action_callback(); + + Variant ret; + Variant item_string = String::utf8([item[0] UTF8String]); + Callable::CallError ce; + const Variant *args[1] = { &item_string }; + + cb.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute help action callback: %s."), Variant::get_callable_error_text(cb, args, 1, ce))); + } + } +} + - (void)forceUnbundledWindowActivationHackStep1 { // Step 1: Switch focus to macOS SystemUIServer process. // Required to perform step 2, TransformProcessType will fail if app is already the in focus. @@ -63,6 +116,13 @@ [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; } +- (void)system_theme_changed:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->emit_system_theme_changed(); + } +} + - (void)applicationDidFinishLaunching:(NSNotification *)notice { NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; NSString *nsbundleid_env = [NSString stringWithUTF8String:getenv("__CFBundleIdentifier")]; @@ -71,6 +131,8 @@ // If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; } + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleInterfaceThemeChangedNotification" object:nil]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleColorPreferencesChangedNotification" object:nil]; } - (id)init { @@ -83,6 +145,11 @@ return self; } +- (void)dealloc { + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleInterfaceThemeChangedNotification" object:nil]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleColorPreferencesChangedNotification" object:nil]; +} + - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); if (!event || !os) { diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 000215ac46..80c9f1b422 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -830,6 +830,7 @@ OS_MacOS::OS_MacOS() { id delegate = [[GodotApplicationDelegate alloc] init]; ERR_FAIL_NULL(delegate); [NSApp setDelegate:delegate]; + [NSApp registerUserInterfaceItemSearchHandler:delegate]; pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 96e2f95abd..7d96bded14 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3494,13 +3494,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_PAINT: { Main::force_redraw(); } break; - case WM_SETTINGCHANGE: { + case WM_SETTINGCHANGE: + case WM_SYSCOLORCHANGE: { if (lParam && CompareStringOrdinal(reinterpret_cast<LPCWCH>(lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } } + if (system_theme_changed.is_valid()) { + system_theme_changed.call(); + } } break; case WM_THEMECHANGED: { if (is_dark_mode_supported() && dark_title_available) { @@ -5058,6 +5062,19 @@ Color DisplayServerWindows::get_accent_color() const { return Color((argb & 0xFF) / 255.f, ((argb & 0xFF00) >> 8) / 255.f, ((argb & 0xFF0000) >> 16) / 255.f, ((argb & 0xFF000000) >> 24) / 255.f); } +Color DisplayServerWindows::get_base_color() const { + if (!ux_theme_available) { + return Color(0, 0, 0, 0); + } + + int argb = GetImmersiveColorFromColorSetEx((UINT)GetImmersiveUserColorSetPreference(false, false), GetImmersiveColorTypeFromName(ShouldAppsUseDarkMode() ? L"ImmersiveDarkChromeMediumLow" : L"ImmersiveLightChromeMediumLow"), false, 0); + return Color((argb & 0xFF) / 255.f, ((argb & 0xFF00) >> 8) / 255.f, ((argb & 0xFF0000) >> 16) / 255.f, ((argb & 0xFF000000) >> 24) / 255.f); +} + +void DisplayServerWindows::set_system_theme_change_callback(const Callable &p_callable) { + system_theme_changed = p_callable; +} + int DisplayServerWindows::tablet_get_driver_count() const { return tablet_drivers.size(); } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index e66c533da5..81cddec49f 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -477,6 +477,8 @@ class DisplayServerWindows : public DisplayServer { CursorShape cursor_shape = CursorShape::CURSOR_ARROW; RBMap<CursorShape, Vector<Variant>> cursors_cache; + Callable system_theme_changed; + void _drag_event(WindowID p_window, float p_x, float p_y, int idx); void _touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx); @@ -522,6 +524,8 @@ public: virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; virtual Color get_accent_color() const override; + virtual Color get_base_color() const override; + virtual void set_system_theme_change_callback(const Callable &p_callable) override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 3b5428d72b..bb787a742a 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -376,6 +376,7 @@ void TabBar::_notification(int p_what) { } queue_redraw(); + update_minimum_size(); [[fallthrough]]; } diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp index 49e565a39c..02e079892c 100644 --- a/scene/main/missing_node.cpp +++ b/scene/main/missing_node.cpp @@ -66,6 +66,14 @@ String MissingNode::get_original_class() const { return original_class; } +void MissingNode::set_original_scene(const String &p_scene) { + original_scene = p_scene; +} + +String MissingNode::get_original_scene() const { + return original_scene; +} + void MissingNode::set_recording_properties(bool p_enable) { recording_properties = p_enable; } @@ -77,8 +85,15 @@ bool MissingNode::is_recording_properties() const { Array MissingNode::get_configuration_warnings() const { // The mere existence of this node is warning. Array ret; - ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class)); - ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss.")); + if (!original_scene.is_empty()) { + ret.push_back(vformat(RTR("This node was an instance of scene '%s', which was no longer available when this scene was loaded."), original_scene)); + ret.push_back(vformat(RTR("Saving current scene will discard instance and all its properties, including editable children edits (if existing)."))); + } else if (!original_class.is_empty()) { + ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class)); + ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss.")); + } else { + ret.push_back(RTR("Unrecognized missing node. Check scene dependency errors for details.")); + } return ret; } @@ -86,11 +101,15 @@ void MissingNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingNode::set_original_class); ClassDB::bind_method(D_METHOD("get_original_class"), &MissingNode::get_original_class); + ClassDB::bind_method(D_METHOD("set_original_scene", "name"), &MissingNode::set_original_class); + ClassDB::bind_method(D_METHOD("get_original_scene"), &MissingNode::get_original_class); + ClassDB::bind_method(D_METHOD("set_recording_properties", "enable"), &MissingNode::set_recording_properties); ClassDB::bind_method(D_METHOD("is_recording_properties"), &MissingNode::is_recording_properties); // Expose, but not save. ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_class", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_class", "get_original_class"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_scene", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_scene", "get_original_scene"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "recording_properties", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_recording_properties", "is_recording_properties"); } diff --git a/scene/main/missing_node.h b/scene/main/missing_node.h index a56469b8ac..ccaf1e471e 100644 --- a/scene/main/missing_node.h +++ b/scene/main/missing_node.h @@ -39,6 +39,7 @@ class MissingNode : public Node { HashMap<StringName, Variant> properties; String original_class; + String original_scene; bool recording_properties = false; protected: @@ -52,6 +53,9 @@ public: void set_original_class(const String &p_class); String get_original_class() const; + void set_original_scene(const String &p_scene); + String get_original_scene() const; + void set_recording_properties(bool p_enable); bool is_recording_properties() const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index e90f163546..a59ac9b56d 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -191,7 +191,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { MissingNode *missing_node = nullptr; if (i == 0 && base_scene_idx >= 0) { - //scene inheritance on root node + // Scene inheritance on root node. Ref<PackedScene> sdata = props[base_scene_idx]; ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); //only main gets main edit state @@ -201,14 +201,22 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } else if (n.instance >= 0) { - //instance a scene into this node + // Instance a scene into this node. if (n.instance & FLAG_INSTANCE_IS_PLACEHOLDER) { - String scene_path = props[n.instance & FLAG_MASK]; + const String scene_path = props[n.instance & FLAG_MASK]; if (disable_placeholders) { Ref<PackedScene> sdata = ResourceLoader::load(scene_path, "PackedScene"); - ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); - ERR_FAIL_NULL_V(node, nullptr); + if (sdata.is_valid()) { + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); + ERR_FAIL_NULL_V(node, nullptr); + } else if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_node = memnew(MissingNode); + missing_node->set_original_scene(scene_path); + missing_node->set_recording_properties(true); + node = missing_node; + } else { + ERR_FAIL_V_MSG(nullptr, "Placeholder scene is missing."); + } } else { InstancePlaceholder *ip = memnew(InstancePlaceholder); ip->set_instance_path(scene_path); @@ -216,14 +224,27 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } node->set_scene_instance_load_placeholder(true); } else { - Ref<PackedScene> sdata = props[n.instance & FLAG_MASK]; - ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); - ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path())); + Ref<Resource> res = props[n.instance & FLAG_MASK]; + Ref<PackedScene> sdata = res; + if (sdata.is_valid()) { + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); + ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path())); + } else if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_node = memnew(MissingNode); +#ifdef TOOLS_ENABLED + if (res.is_valid()) { + missing_node->set_original_scene(res->get_meta("__load_path__", "")); + } +#endif + missing_node->set_recording_properties(true); + node = missing_node; + } else { + ERR_FAIL_V_MSG(nullptr, "Scene instance is missing."); + } } } else if (n.type == TYPE_INSTANTIATED) { - //get the node from somewhere, it likely already exists from another instance + // Get the node from somewhere, it likely already exists from another instance. if (parent) { node = parent->_get_child_by_name(snames[n.name]); #ifdef DEBUG_ENABLED @@ -233,7 +254,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { #endif } } else { - //node belongs to this scene and must be created + // Node belongs to this scene and must be created. Object *obj = ClassDB::instantiate(snames[n.type]); node = Object::cast_to<Node>(obj); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index a97ff5054d..d08919a8c6 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -176,6 +176,13 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R } else { r_res = Ref<Resource>(); } +#ifdef TOOLS_ENABLED + if (r_res.is_null()) { + // Hack to allow checking original path. + r_res.instantiate(); + r_res->set_meta("__load_path__", ext_resources[id].path); + } +#endif } VariantParser::get_token(p_stream, token, line, r_err_str); diff --git a/scene/resources/texture_rd.cpp b/scene/resources/texture_rd.cpp index 49e13824a9..531dbcbe7e 100644 --- a/scene/resources/texture_rd.cpp +++ b/scene/resources/texture_rd.cpp @@ -74,27 +74,7 @@ void Texture2DRD::set_texture_rd_rid(RID p_texture_rd_rid) { ERR_FAIL_NULL(RS::get_singleton()); if (p_texture_rd_rid.is_valid()) { - ERR_FAIL_NULL(RD::get_singleton()); - ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); - - RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); - ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D); - ERR_FAIL_COND(tf.depth > 1); - ERR_FAIL_COND(tf.array_layers > 1); - - size.width = tf.width; - size.height = tf.height; - - texture_rd_rid = p_texture_rd_rid; - - if (texture_rid.is_valid()) { - RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid)); - } else { - texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid); - } - - notify_property_list_changed(); - emit_changed(); + RS::get_singleton()->call_on_render_thread(callable_mp(this, &Texture2DRD::_set_texture_rd_rid).bind(p_texture_rd_rid)); } else if (texture_rid.is_valid()) { RS::get_singleton()->free(texture_rid); texture_rid = RID(); @@ -105,6 +85,30 @@ void Texture2DRD::set_texture_rd_rid(RID p_texture_rd_rid) { } } +void Texture2DRD::_set_texture_rd_rid(RID p_texture_rd_rid) { + ERR_FAIL_NULL(RD::get_singleton()); + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); + + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); + ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D); + ERR_FAIL_COND(tf.depth > 1); + ERR_FAIL_COND(tf.array_layers > 1); + + size.width = tf.width; + size.height = tf.height; + + texture_rd_rid = p_texture_rd_rid; + + if (texture_rid.is_valid()) { + RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid)); + } else { + texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid); + } + + notify_property_list_changed(); + emit_changed(); +} + RID Texture2DRD::get_texture_rd_rid() const { return texture_rd_rid; } @@ -173,48 +177,7 @@ void TextureLayeredRD::set_texture_rd_rid(RID p_texture_rd_rid) { ERR_FAIL_NULL(RS::get_singleton()); if (p_texture_rd_rid.is_valid()) { - ERR_FAIL_NULL(RD::get_singleton()); - ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); - - RS::TextureLayeredType rs_layer_type; - RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); - ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D_ARRAY); - ERR_FAIL_COND(tf.depth > 1); - switch (layer_type) { - case LAYERED_TYPE_2D_ARRAY: { - ERR_FAIL_COND(tf.array_layers <= 1); - rs_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY; - } break; - case LAYERED_TYPE_CUBEMAP: { - ERR_FAIL_COND(tf.array_layers != 6); - rs_layer_type = RS::TEXTURE_LAYERED_CUBEMAP; - } break; - case LAYERED_TYPE_CUBEMAP_ARRAY: { - ERR_FAIL_COND((tf.array_layers == 0) || ((tf.array_layers % 6) != 0)); - rs_layer_type = RS::TEXTURE_LAYERED_CUBEMAP_ARRAY; - } break; - default: { - ERR_FAIL_MSG("Unknown layer type selected"); - } break; - } - - size.width = tf.width; - size.height = tf.height; - layers = tf.array_layers; - mipmaps = tf.mipmaps; - - texture_rd_rid = p_texture_rd_rid; - - if (texture_rid.is_valid()) { - RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid, rs_layer_type)); - } else { - texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid, rs_layer_type); - } - - image_format = RS::get_singleton()->texture_get_format(texture_rid); - - notify_property_list_changed(); - emit_changed(); + RS::get_singleton()->call_on_render_thread(callable_mp(this, &TextureLayeredRD::_set_texture_rd_rid).bind(p_texture_rd_rid)); } else if (texture_rid.is_valid()) { RS::get_singleton()->free(texture_rid); texture_rid = RID(); @@ -228,6 +191,51 @@ void TextureLayeredRD::set_texture_rd_rid(RID p_texture_rd_rid) { } } +void TextureLayeredRD::_set_texture_rd_rid(RID p_texture_rd_rid) { + ERR_FAIL_NULL(RD::get_singleton()); + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); + + RS::TextureLayeredType rs_layer_type; + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); + ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D_ARRAY); + ERR_FAIL_COND(tf.depth > 1); + switch (layer_type) { + case LAYERED_TYPE_2D_ARRAY: { + ERR_FAIL_COND(tf.array_layers <= 1); + rs_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY; + } break; + case LAYERED_TYPE_CUBEMAP: { + ERR_FAIL_COND(tf.array_layers != 6); + rs_layer_type = RS::TEXTURE_LAYERED_CUBEMAP; + } break; + case LAYERED_TYPE_CUBEMAP_ARRAY: { + ERR_FAIL_COND((tf.array_layers == 0) || ((tf.array_layers % 6) != 0)); + rs_layer_type = RS::TEXTURE_LAYERED_CUBEMAP_ARRAY; + } break; + default: { + ERR_FAIL_MSG("Unknown layer type selected"); + } break; + } + + size.width = tf.width; + size.height = tf.height; + layers = tf.array_layers; + mipmaps = tf.mipmaps; + + texture_rd_rid = p_texture_rd_rid; + + if (texture_rid.is_valid()) { + RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid, rs_layer_type)); + } else { + texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid, rs_layer_type); + } + + image_format = RS::get_singleton()->texture_get_format(texture_rid); + + notify_property_list_changed(); + emit_changed(); +} + RID TextureLayeredRD::get_texture_rd_rid() const { return texture_rd_rid; } @@ -291,30 +299,7 @@ void Texture3DRD::set_texture_rd_rid(RID p_texture_rd_rid) { ERR_FAIL_NULL(RS::get_singleton()); if (p_texture_rd_rid.is_valid()) { - ERR_FAIL_NULL(RD::get_singleton()); - ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); - - RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); - ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_3D); - ERR_FAIL_COND(tf.array_layers > 1); - - size.x = tf.width; - size.y = tf.height; - size.z = tf.depth; - mipmaps = tf.mipmaps; - - texture_rd_rid = p_texture_rd_rid; - - if (texture_rid.is_valid()) { - RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid)); - } else { - texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid); - } - - image_format = RS::get_singleton()->texture_get_format(texture_rid); - - notify_property_list_changed(); - emit_changed(); + RS::get_singleton()->call_on_render_thread(callable_mp(this, &Texture3DRD::_set_texture_rd_rid).bind(p_texture_rd_rid)); } else if (texture_rid.is_valid()) { RS::get_singleton()->free(texture_rid); texture_rid = RID(); @@ -327,6 +312,33 @@ void Texture3DRD::set_texture_rd_rid(RID p_texture_rd_rid) { } } +void Texture3DRD::_set_texture_rd_rid(RID p_texture_rd_rid) { + ERR_FAIL_NULL(RD::get_singleton()); + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); + + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); + ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_3D); + ERR_FAIL_COND(tf.array_layers > 1); + + size.x = tf.width; + size.y = tf.height; + size.z = tf.depth; + mipmaps = tf.mipmaps; + + texture_rd_rid = p_texture_rd_rid; + + if (texture_rid.is_valid()) { + RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid)); + } else { + texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid); + } + + image_format = RS::get_singleton()->texture_get_format(texture_rid); + + notify_property_list_changed(); + emit_changed(); +} + RID Texture3DRD::get_texture_rd_rid() const { return texture_rd_rid; } diff --git a/scene/resources/texture_rd.h b/scene/resources/texture_rd.h index f88d6c5155..5dc286cdeb 100644 --- a/scene/resources/texture_rd.h +++ b/scene/resources/texture_rd.h @@ -60,6 +60,9 @@ public: void set_texture_rd_rid(RID p_texture_rd_rid); RID get_texture_rd_rid() const; + // Internal function that should only be called from the rendering thread. + void _set_texture_rd_rid(RID p_texture_rd_rid); + Texture2DRD(); ~Texture2DRD(); }; @@ -94,6 +97,9 @@ public: void set_texture_rd_rid(RID p_texture_rd_rid); RID get_texture_rd_rid() const; + // Internal function that should only be called from the rendering thread. + void _set_texture_rd_rid(RID p_texture_rd_rid); + TextureLayeredRD(LayeredType p_layer_type); ~TextureLayeredRD(); }; @@ -146,6 +152,9 @@ public: void set_texture_rd_rid(RID p_texture_rd_rid); RID get_texture_rd_rid() const; + // Internal function that should only be called from the rendering thread. + void _set_texture_rd_rid(RID p_texture_rd_rid); + Texture3DRD(); ~Texture3DRD(); }; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 0496fb9180..6631d44f63 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -47,6 +47,10 @@ DisplayServer::DisplayServerCreate DisplayServer::server_create_functions[Displa int DisplayServer::server_create_count = 1; +void DisplayServer::help_set_search_callbacks(const Callable &p_search_callback, const Callable &p_action_callback) { + WARN_PRINT("Native help is not supported by this display server."); +} + int DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); return -1; @@ -633,6 +637,8 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature); ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name); + ClassDB::bind_method(D_METHOD("help_set_search_callbacks", "search_callback", "action_callback"), &DisplayServer::help_set_search_callbacks); + ClassDB::bind_method(D_METHOD("global_menu_set_popup_callbacks", "menu_root", "open_callback", "close_callback"), &DisplayServer::global_menu_set_popup_callbacks); ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); @@ -705,6 +711,8 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_dark_mode_supported"), &DisplayServer::is_dark_mode_supported); ClassDB::bind_method(D_METHOD("is_dark_mode"), &DisplayServer::is_dark_mode); ClassDB::bind_method(D_METHOD("get_accent_color"), &DisplayServer::get_accent_color); + ClassDB::bind_method(D_METHOD("get_base_color"), &DisplayServer::get_base_color); + ClassDB::bind_method(D_METHOD("set_system_theme_change_callback", "callable"), &DisplayServer::set_system_theme_change_callback); ClassDB::bind_method(D_METHOD("mouse_set_mode", "mouse_mode"), &DisplayServer::mouse_set_mode); ClassDB::bind_method(D_METHOD("mouse_get_mode"), &DisplayServer::mouse_get_mode); @@ -879,6 +887,7 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_EXTEND_TO_TITLE); BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE); BIND_ENUM_CONSTANT(FEATURE_STATUS_INDICATOR); + BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); diff --git a/servers/display_server.h b/servers/display_server.h index 6e5a4655a2..0f6c43ebe0 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -126,11 +126,14 @@ public: FEATURE_EXTEND_TO_TITLE, FEATURE_SCREEN_CAPTURE, FEATURE_STATUS_INDICATOR, + FEATURE_NATIVE_HELP, }; virtual bool has_feature(Feature p_feature) const = 0; virtual String get_name() const = 0; + virtual void help_set_search_callbacks(const Callable &p_search_callback = Callable(), const Callable &p_action_callback = Callable()); + virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()); virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1); @@ -224,7 +227,9 @@ public: virtual bool is_dark_mode_supported() const { return false; }; virtual bool is_dark_mode() const { return false; }; - virtual Color get_accent_color() const { return Color(0, 0, 0, 0); }; + virtual Color get_accent_color() const { return Color(0, 0, 0, 0); } + virtual Color get_base_color() const { return Color(0, 0, 0, 0); } + virtual void set_system_theme_change_callback(const Callable &p_callable) {} private: static bool window_early_clear_override_enabled; diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index c665b02e14..1304b284d5 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -172,7 +172,7 @@ private: void cleanup(); }; - //textures can be created from threads, so this RID_Owner is thread safe + // Textures can be created from threads, so this RID_Owner is thread safe. mutable RID_Owner<Texture, true> texture_owner; Texture *get_texture(RID p_rid) { return texture_owner.get_or_null(p_rid); }; |