diff options
47 files changed, 733 insertions, 366 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 562bde978e..9fe54e57a7 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -348,7 +348,6 @@ bool ProjectSettings::_get(const StringName &p_name, Variant &r_ret) const { _THREAD_SAFE_METHOD_ if (!props.has(p_name)) { - WARN_PRINT("Property not found: " + String(p_name)); return false; } r_ret = props[p_name].variant; diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index ddf90f6130..66b0161160 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -507,6 +507,14 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_ return ret; } +static GDObjectInstanceID gdextension_variant_get_object_instance_id(GDExtensionConstVariantPtr p_self) { + const Variant *self = (const Variant *)p_self; + if (likely(self->get_type() == Variant::OBJECT)) { + return self->operator ObjectID(); + } + return 0; +} + static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) { String name = Variant::get_type_name((Variant::Type)p_type); memnew_placement(r_ret, String(name)); @@ -1610,6 +1618,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(variant_has_method); REGISTER_INTERFACE_FUNC(variant_has_member); REGISTER_INTERFACE_FUNC(variant_has_key); + REGISTER_INTERFACE_FUNC(variant_get_object_instance_id); REGISTER_INTERFACE_FUNC(variant_get_type_name); REGISTER_INTERFACE_FUNC(variant_can_convert); REGISTER_INTERFACE_FUNC(variant_can_convert_strict); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 9e3ce25698..374dbfd071 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -1308,6 +1308,21 @@ typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVaria typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid); /** + * @name variant_get_object_instance_id + * @since 4.4 + * + * Gets the object instance ID from a variant of type GDEXTENSION_VARIANT_TYPE_OBJECT. + * + * If the variant isn't of type GDEXTENSION_VARIANT_TYPE_OBJECT, then zero will be returned. + * The instance ID will be returned even if the object is no longer valid - use `object_get_instance_by_id()` to check if the object is still valid. + * + * @param p_self A pointer to the Variant. + * + * @return The instance ID for the contained object. + */ +typedef GDObjectInstanceID (*GDExtensionInterfaceVariantGetObjectInstanceId)(GDExtensionConstVariantPtr p_self); + +/** * @name variant_get_type_name * @since 4.1 * diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 5f8a4b85a4..0ff4fbe490 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -99,31 +99,42 @@ void Resource::set_path_cache(const String &p_path) { GDVIRTUAL_CALL(_set_path_cache, p_path); } +static thread_local RandomPCG unique_id_gen(0, RandomPCG::DEFAULT_INC); + +void Resource::seed_scene_unique_id(uint32_t p_seed) { + unique_id_gen.seed(p_seed); +} + String Resource::generate_scene_unique_id() { // Generate a unique enough hash, but still user-readable. // If it's not unique it does not matter because the saver will try again. - OS::DateTime dt = OS::get_singleton()->get_datetime(); - uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec()); - hash = hash_murmur3_one_32(dt.year, hash); - hash = hash_murmur3_one_32(dt.month, hash); - hash = hash_murmur3_one_32(dt.day, hash); - hash = hash_murmur3_one_32(dt.hour, hash); - hash = hash_murmur3_one_32(dt.minute, hash); - hash = hash_murmur3_one_32(dt.second, hash); - hash = hash_murmur3_one_32(Math::rand(), hash); + if (unique_id_gen.get_seed() == 0) { + OS::DateTime dt = OS::get_singleton()->get_datetime(); + uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec()); + hash = hash_murmur3_one_32(dt.year, hash); + hash = hash_murmur3_one_32(dt.month, hash); + hash = hash_murmur3_one_32(dt.day, hash); + hash = hash_murmur3_one_32(dt.hour, hash); + hash = hash_murmur3_one_32(dt.minute, hash); + hash = hash_murmur3_one_32(dt.second, hash); + hash = hash_murmur3_one_32(Math::rand(), hash); + unique_id_gen.seed(hash); + } + + uint32_t random_num = unique_id_gen.rand(); static constexpr uint32_t characters = 5; static constexpr uint32_t char_count = ('z' - 'a'); static constexpr uint32_t base = char_count + ('9' - '0'); String id; for (uint32_t i = 0; i < characters; i++) { - uint32_t c = hash % base; + uint32_t c = random_num % base; if (c < char_count) { id += String::chr('a' + c); } else { id += String::chr('0' + (c - char_count)); } - hash /= base; + random_num /= base; } return id; diff --git a/core/io/resource.h b/core/io/resource.h index 8966c0233c..015f7ad197 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -114,6 +114,7 @@ public: virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache. _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); } + static void seed_scene_unique_id(uint32_t p_seed); static String generate_scene_unique_id(); void set_scene_unique_id(const String &p_id); String get_scene_unique_id() const; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index b4826c356e..109999d612 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -845,29 +845,27 @@ Error ResourceLoaderBinary::load() { } } - if (ClassDB::has_property(res->get_class_name(), name)) { - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = res->get(name, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); - } + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); } } + } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary set_dict = value; - bool is_get_valid = false; - Variant get_value = res->get(name, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { - Dictionary get_dict = get_value; - if (!set_dict.is_same_typed(get_dict)) { - value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), - get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); - } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } } } @@ -2136,6 +2134,8 @@ static String _resource_get_class(Ref<Resource> p_resource) { } Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { + Resource::seed_scene_unique_id(p_path.hash()); + Error err; Ref<FileAccess> f; if (p_flags & ResourceSaver::FLAG_COMPRESS) { diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml index 71c4ca0f9b..fb90a2a5cd 100644 --- a/doc/classes/EditorContextMenuPlugin.xml +++ b/doc/classes/EditorContextMenuPlugin.xml @@ -47,6 +47,24 @@ [/codeblock] </description> </method> + <method name="add_context_submenu_item"> + <return type="void" /> + <param index="0" name="name" type="String" /> + <param index="1" name="menu" type="PopupMenu" /> + <param index="2" name="icon" type="Texture2D" default="null" /> + <description> + Add a submenu to the context menu of the plugin's specified slot. The submenu is not automatically handled, you need to connect to its signals yourself. Also the submenu is freed on every popup, so provide a new [PopupMenu] every time. + [codeblock] + func _popup_menu(paths): + var popup_menu = PopupMenu.new() + popup_menu.add_item("Blue") + popup_menu.add_item("White") + popup_menu.id_pressed.connect(_on_color_submenu_option) + + add_context_menu_item("Set Node Color", popup_menu) + [/codeblock] + </description> + </method> <method name="add_menu_shortcut"> <return type="void" /> <param index="0" name="shortcut" type="Shortcut" /> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index 795c5c1c2f..43059db8b2 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -343,6 +343,14 @@ [/codeblock] </description> </method> + <method name="popup_quick_open"> + <return type="void" /> + <param index="0" name="callback" type="Callable" /> + <param index="1" name="base_types" type="StringName[]" default="[]" /> + <description> + Pops up an editor dialog for quick selecting a resource file. The [param callback] must take a single argument of type [String] which will contain the path of the selected resource or be empty if the dialog is canceled. If [param base_types] is provided, the dialog will only show resources that match these types. Only types deriving from [Resource] are supported. + </description> + </method> <method name="reload_scene_from_path"> <return type="void" /> <param index="0" name="scene_filepath" type="String" /> diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index 41f42392de..3e0c328dcb 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -8,7 +8,7 @@ - When the [LineEdit] control is focused using the keyboard arrow keys, it will only gain focus and not enter edit mode. - To enter edit mode, click on the control with the mouse or press the [code]ui_text_submit[/code] action (by default [kbd]Enter[/kbd] or [kbd]Kp Enter[/kbd]). - To exit edit mode, press [code]ui_text_submit[/code] or [code]ui_cancel[/code] (by default [kbd]Escape[/kbd]) actions. - - Check [method is_editing] and [signal editing_toggled] for more information. + - Check [method edit], [method unedit], [method is_editing], and [signal editing_toggled] for more information. [b]Important:[/b] - Focusing the [LineEdit] with [code]ui_focus_next[/code] (by default [kbd]Tab[/kbd]) or [code]ui_focus_prev[/code] (by default [kbd]Shift + Tab[/kbd]) or [method Control.grab_focus] still enters edit mode (for compatibility). [LineEdit] features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS): @@ -75,6 +75,13 @@ Clears the current selection. </description> </method> + <method name="edit"> + <return type="void" /> + <description> + Allows entering edit mode whether the [LineEdit] is focused or not. + Use [method Callable.call_deferred] if you want to enter edit mode on [signal text_submitted]. + </description> + </method> <method name="get_menu" qualifiers="const"> <return type="PopupMenu" /> <description> @@ -223,6 +230,12 @@ Selects the whole [String]. </description> </method> + <method name="unedit"> + <return type="void" /> + <description> + Allows exiting edit mode while preserving focus. + </description> + </method> </methods> <members> <member name="alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0"> diff --git a/drivers/SCsub b/drivers/SCsub index 219c4451ee..e0bfa138f5 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -1,6 +1,8 @@ #!/usr/bin/env python from misc.utility.scons_hints import * +from methods import print_error + Import("env") env.drivers_sources = [] @@ -20,7 +22,7 @@ if env["platform"] == "windows": SConscript("backtrace/SCsub") if env["xaudio2"]: if "xaudio2" not in supported: - print("Target platform '{}' does not support the XAudio2 audio driver. Aborting.".format(env["platform"])) + print_error("Target platform '{}' does not support the XAudio2 audio driver".format(env["platform"])) Exit(255) SConscript("xaudio2/SCsub") @@ -34,7 +36,7 @@ if env["vulkan"]: SConscript("vulkan/SCsub") if env["d3d12"]: if "d3d12" not in supported: - print("Target platform '{}' does not support the D3D12 rendering driver. Aborting.".format(env["platform"])) + print_error("Target platform '{}' does not support the D3D12 rendering driver".format(env["platform"])) Exit(255) SConscript("d3d12/SCsub") if env["opengl3"]: @@ -43,7 +45,7 @@ if env["opengl3"]: SConscript("egl/SCsub") if env["metal"]: if "metal" not in supported: - print("Target platform '{}' does not support the Metal rendering driver. Aborting.".format(env["platform"])) + print_error("Target platform '{}' does not support the Metal rendering driver".format(env["platform"])) Exit(255) SConscript("metal/SCsub") diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 9d691a0d23..0f7faaddf0 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -358,7 +358,11 @@ RDD::TextureID RenderingDeviceDriverMetal::texture_create(const TextureFormat &p } RDD::TextureID RenderingDeviceDriverMetal::texture_create_from_extension(uint64_t p_native_texture, TextureType p_type, DataFormat p_format, uint32_t p_array_layers, bool p_depth_stencil) { - ERR_FAIL_V_MSG(RDD::TextureID(), "not implemented"); + id<MTLTexture> obj = (__bridge id<MTLTexture>)(void *)(uintptr_t)p_native_texture; + + // We only need to create a RDD::TextureID for an existing, natively-provided texture. + + return rid::make(obj); } RDD::TextureID RenderingDeviceDriverMetal::texture_create_shared(TextureID p_original_texture, const TextureView &p_view) { diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 07547fee8a..88a32b1a6d 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -644,6 +644,8 @@ void FindReplaceBar::_search_text_submitted(const String &p_text) { } else { search_next(); } + + callable_mp(search_text, &LineEdit::edit).call_deferred(); } void FindReplaceBar::_replace_text_submitted(const String &p_text) { diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index fa6198f695..264c80dcbf 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -40,6 +40,7 @@ #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/filesystem_dock.h" +#include "editor/gui/editor_quick_open_dialog.h" #include "editor/gui/editor_run_bar.h" #include "editor/gui/editor_scene_tabs.h" #include "editor/gui/scene_tree_editor.h" @@ -336,6 +337,24 @@ void EditorInterface::popup_property_selector(Object *p_object, const Callable & property_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED); } +void EditorInterface::popup_quick_open(const Callable &p_callback, const TypedArray<StringName> &p_base_types) { + StringName required_type = SNAME("Resource"); + Vector<StringName> base_types; + if (p_base_types.is_empty()) { + base_types.append(required_type); + } else { + for (int i = 0; i < p_base_types.size(); i++) { + StringName type = p_base_types[i]; + ERR_FAIL_COND_MSG(!(ClassDB::is_parent_class(type, required_type) || EditorNode::get_editor_data().script_class_is_parent(type, required_type)), "Only types deriving from Resource are supported in the quick open dialog."); + base_types.append(type); + } + } + + EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog(); + quick_open->connect(SNAME("canceled"), callable_mp(this, &EditorInterface::_quick_open).bind(String(), p_callback)); + quick_open->popup_dialog(base_types, callable_mp(this, &EditorInterface::_quick_open).bind(p_callback)); +} + void EditorInterface::_node_selected(const NodePath &p_node_path, const Callable &p_callback) { const NodePath path = get_edited_scene_root()->get_path().rel_path_to(p_node_path); _call_dialog_callback(p_callback, path, "node selected"); @@ -353,6 +372,12 @@ void EditorInterface::_property_selection_canceled(const Callable &p_callback) { _call_dialog_callback(p_callback, NodePath(), "property selection canceled"); } +void EditorInterface::_quick_open(const String &p_file_path, const Callable &p_callback) { + EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog(); + quick_open->disconnect(SNAME("canceled"), callable_mp(this, &EditorInterface::_quick_open)); + _call_dialog_callback(p_callback, p_file_path, "quick open"); +} + void EditorInterface::_call_dialog_callback(const Callable &p_callback, const Variant &p_selected, const String &p_context) { Callable::CallError ce; Variant ret; @@ -568,6 +593,7 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types", "current_value"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter", "current_value"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("popup_quick_open", "callback", "base_types"), &EditorInterface::popup_quick_open, DEFVAL(TypedArray<StringName>())); // Editor docks. diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 20d66d71f5..4877444dac 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -72,6 +72,7 @@ class EditorInterface : public Object { void _node_selection_canceled(const Callable &p_callback); void _property_selected(const String &p_property_name, const Callable &p_callback); void _property_selection_canceled(const Callable &p_callback); + void _quick_open(const String &p_file_path, const Callable &p_callback); void _call_dialog_callback(const Callable &p_callback, const Variant &p_selected, const String &p_context); // Editor tools. @@ -138,6 +139,7 @@ public: void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>(), Node *p_current_value = nullptr); // Must use Vector<int> because exposing Vector<Variant::Type> is not supported. void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array(), const String &p_current_value = String()); + void popup_quick_open(const Callable &p_callback, const TypedArray<StringName> &p_base_types = TypedArray<StringName>()); // Editor docks. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 2853ebc499..d88fb134f1 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1983,7 +1983,7 @@ void EditorNode::try_autosave() { editor_data.save_editor_external_data(); } -void EditorNode::restart_editor() { +void EditorNode::restart_editor(bool p_goto_project_manager) { exiting = true; if (project_run_bar->is_playing()) { @@ -1991,22 +1991,25 @@ void EditorNode::restart_editor() { } String to_reopen; - if (get_tree()->get_edited_scene_root()) { + if (!p_goto_project_manager && get_tree()->get_edited_scene_root()) { to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path(); } _exit_editor(EXIT_SUCCESS); List<String> args; - for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) { args.push_back(a); } - args.push_back("--path"); - args.push_back(ProjectSettings::get_singleton()->get_resource_path()); + if (p_goto_project_manager) { + args.push_back("--project-manager"); + } else { + args.push_back("--path"); + args.push_back(ProjectSettings::get_singleton()->get_resource_path()); - args.push_back("-e"); + args.push_back("-e"); + } if (!to_reopen.is_empty()) { args.push_back(to_reopen); @@ -2387,7 +2390,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) { // This is to sweep properties that were removed from the inspector. List<ObjectID> to_remove; for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) { - const Object *context = ObjectDB::get_instance(kv.key); + Object *context = ObjectDB::get_instance(kv.key); if (context) { // In case of self-owning plugins, they are disabled here if they can auto hide. const EditorPlugin *self_owning = Object::cast_to<EditorPlugin>(context); @@ -2396,7 +2399,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) { } } - if (!context) { + if (!context || context->call(SNAME("_should_stop_editing"))) { to_remove.push_back(kv.key); for (EditorPlugin *plugin : kv.value) { if (plugin->can_auto_hide()) { @@ -3402,23 +3405,7 @@ void EditorNode::_discard_changes(const String &p_str) { } break; case RUN_PROJECT_MANAGER: { - project_run_bar->stop_playing(); - _exit_editor(EXIT_SUCCESS); - String exec = OS::get_singleton()->get_executable_path(); - - List<String> args; - for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) { - args.push_back(a); - } - - String exec_base_dir = exec.get_base_dir(); - if (!exec_base_dir.is_empty()) { - args.push_back("--path"); - args.push_back(exec_base_dir); - } - args.push_back("--project-manager"); - - OS::get_singleton()->set_restart_on_exit(true, args); + restart_editor(true); } break; case RELOAD_CURRENT_PROJECT: { restart_editor(); diff --git a/editor/editor_node.h b/editor/editor_node.h index 55caed4bb4..696caf857c 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -926,7 +926,7 @@ public: void save_scene_list(const HashSet<String> &p_scene_paths); void save_before_run(); void try_autosave(); - void restart_editor(); + void restart_editor(bool p_goto_project_manager = false); void unload_editor_addons(); void dim_editor(bool p_dimming); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 897e7835fd..c5a35e466c 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3179,6 +3179,10 @@ void EditorPropertyResource::_update_preferred_shader() { } } +bool EditorPropertyResource::_should_stop_editing() const { + return !resource_picker->is_toggle_pressed(); +} + void EditorPropertyResource::_viewport_selected(const NodePath &p_path) { Node *to_node = get_node(p_path); if (!Object::cast_to<Viewport>(to_node)) { @@ -3353,13 +3357,18 @@ void EditorPropertyResource::_notification(int p_what) { switch (p_what) { case NOTIFICATION_EXIT_TREE: { const EditorInspector *ei = get_parent_inspector(); - if (ei && !ei->is_main_editor_inspector()) { + const EditorInspector *main_ei = InspectorDock::get_inspector_singleton(); + if (ei && main_ei && ei != main_ei && !main_ei->is_ancestor_of(ei)) { fold_resource(); } } break; } } +void EditorPropertyResource::_bind_methods() { + ClassDB::bind_method(D_METHOD("_should_stop_editing"), &EditorPropertyResource::_should_stop_editing); +} + EditorPropertyResource::EditorPropertyResource() { use_sub_inspector = bool(EDITOR_GET("interface/inspector/open_resources_in_current_inspector")); has_borders = true; diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 2ec78cdb44..004630da3e 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -683,10 +683,12 @@ class EditorPropertyResource : public EditorProperty { void _open_editor_pressed(); void _update_preferred_shader(); + bool _should_stop_editing() const; protected: virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); + static void _bind_methods(); public: virtual void update_property() override; diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index e4ae2a6202..0f0287718c 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -132,6 +132,11 @@ void EditorResourcePicker::_resource_selected() { emit_signal(SNAME("resource_selected"), edited_resource, false); } +void EditorResourcePicker::_resource_changed() { + emit_signal(SNAME("resource_changed"), edited_resource); + _update_resource(); +} + void EditorResourcePicker::_file_selected(const String &p_path) { Ref<Resource> loaded_resource = ResourceLoader::load(p_path); ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + p_path + "'."); @@ -167,8 +172,7 @@ void EditorResourcePicker::_file_selected(const String &p_path) { } edited_resource = loaded_resource; - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } void EditorResourcePicker::_resource_saved(Object *p_resource) { @@ -353,8 +357,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { case OBJ_MENU_CLEAR: { edited_resource = Ref<Resource>(); - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } break; case OBJ_MENU_MAKE_UNIQUE: { @@ -366,8 +369,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail. edited_resource = unique_resource; - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } break; case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: { @@ -432,9 +434,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { _edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE); return; } - - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } break; case OBJ_MENU_SHOW_IN_FILE_SYSTEM: { @@ -453,8 +453,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { ERR_FAIL_INDEX(to_type, conversions.size()); edited_resource = conversions[to_type]->convert(edited_resource); - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); break; } @@ -481,8 +480,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { // Prevent freeing of the object until the end of the update of the resource (GH-88286). Ref<Resource> old_edited_resource = edited_resource; edited_resource = Ref<Resource>(resp); - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } break; } } @@ -778,8 +776,7 @@ void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_ } edited_resource = dropped_resource; - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } } @@ -952,6 +949,10 @@ void EditorResourcePicker::set_toggle_pressed(bool p_pressed) { assign_button->set_pressed(p_pressed); } +bool EditorResourcePicker::is_toggle_pressed() const { + return assign_button->is_pressed(); +} + void EditorResourcePicker::set_editable(bool p_editable) { editable = p_editable; assign_button->set_disabled(!editable && !edited_resource.is_valid()); @@ -1046,8 +1047,7 @@ void EditorResourcePicker::_duplicate_selected_resources() { if (meta.size() == 1) { // Root. edited_resource = unique_resource; - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + _resource_changed(); } else { Array parent_meta = item->get_parent()->get_metadata(0); Ref<Resource> parent = parent_meta[0]; diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index c39d9af764..0a32dea3ed 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -86,6 +86,7 @@ class EditorResourcePicker : public HBoxContainer { void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj); void _resource_selected(); + void _resource_changed(); void _file_selected(const String &p_path); void _resource_saved(Object *p_resource); @@ -134,6 +135,7 @@ public: void set_toggle_mode(bool p_enable); bool is_toggle_mode() const; void set_toggle_pressed(bool p_pressed); + bool is_toggle_pressed() const; void set_editable(bool p_editable); bool is_editable() const; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index ceaffb64c4..ee06f08a2d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -221,7 +221,6 @@ bool EditorSettings::_get(const StringName &p_name, Variant &r_ret) const { const VariantContainer *v = props.getptr(p_name); if (!v) { - WARN_PRINT("EditorSettings::_get - Property not found: " + String(p_name)); return false; } r_ret = v->variant; diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index a63c3f7848..77d0ba7a60 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -1580,7 +1580,7 @@ void EditorFileDialog::_favorite_move_down() { } void EditorFileDialog::_update_favorites() { - bool res = (access == ACCESS_RESOURCES); + bool access_resources = (access == ACCESS_RESOURCES); String current = get_current_dir(); favorites->clear(); @@ -1596,8 +1596,11 @@ void EditorFileDialog::_update_favorites() { for (int i = 0; i < favorited.size(); i++) { String name = favorited[i]; - bool cres = name.begins_with("res://"); - if (cres != res || !name.ends_with("/")) { + if (access_resources != name.begins_with("res://")) { + continue; + } + + if (!name.ends_with("/")) { continue; } @@ -1609,7 +1612,7 @@ void EditorFileDialog::_update_favorites() { } // Compute favorite display text. - if (res && name == "res://") { + if (access_resources && name == "res://") { if (name == current) { current_favorite = favorited_paths.size(); } @@ -1620,7 +1623,7 @@ void EditorFileDialog::_update_favorites() { if (name == current || name == current + "/") { current_favorite = favorited_paths.size(); } - name = name.substr(0, name.length() - 1); + name = name.trim_suffix("/"); name = name.get_file(); favorited_paths.append(favorited[i]); favorited_names.append(name); @@ -1647,7 +1650,7 @@ void EditorFileDialog::_update_favorites() { } void EditorFileDialog::_favorite_pressed() { - bool res = (access == ACCESS_RESOURCES); + bool access_resources = (access == ACCESS_RESOURCES); String cd = get_current_dir(); if (!cd.ends_with("/")) { @@ -1657,13 +1660,12 @@ void EditorFileDialog::_favorite_pressed() { Vector<String> favorited = EditorSettings::get_singleton()->get_favorites(); bool found = false; - for (int i = 0; i < favorited.size(); i++) { - bool cres = favorited[i].begins_with("res://"); - if (cres != res) { + for (const String &name : favorited) { + if (access_resources != name.begins_with("res://")) { continue; } - if (favorited[i] == cd) { + if (name == cd) { found = true; break; } @@ -1683,31 +1685,30 @@ void EditorFileDialog::_favorite_pressed() { void EditorFileDialog::_update_recent() { recent->clear(); - bool res = (access == ACCESS_RESOURCES); + bool access_resources = (access == ACCESS_RESOURCES); Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs(); Vector<String> recentd_paths; Vector<String> recentd_names; + bool modified = false; for (int i = 0; i < recentd.size(); i++) { - bool cres = recentd[i].begins_with("res://"); - if (cres != res) { + String name = recentd[i]; + if (access_resources != name.begins_with("res://")) { continue; } - if (!dir_access->dir_exists(recentd[i])) { + if (!dir_access->dir_exists(name)) { // Remove invalid directory from the list of Recent directories. recentd.remove_at(i--); + modified = true; continue; } // Compute recent directory display text. - String name = recentd[i]; - if (res && name == "res://") { + if (access_resources && name == "res://") { name = "/"; } else { - if (name.ends_with("/")) { - name = name.substr(0, name.length() - 1); - } + name = name.trim_suffix("/"); name = name.get_file(); } recentd_paths.append(recentd[i]); @@ -1721,7 +1722,10 @@ void EditorFileDialog::_update_recent() { recent->set_item_metadata(-1, recentd_paths[i]); recent->set_item_icon_modulate(-1, get_dir_icon_color(recentd_paths[i])); } - EditorSettings::get_singleton()->set_recent_dirs(recentd); + + if (modified) { + EditorSettings::get_singleton()->set_recent_dirs(recentd); + } } void EditorFileDialog::_recent_selected(int p_idx) { diff --git a/editor/gui/editor_quick_open_dialog.cpp b/editor/gui/editor_quick_open_dialog.cpp index 83b11e7022..94a5ff94a3 100644 --- a/editor/gui/editor_quick_open_dialog.cpp +++ b/editor/gui/editor_quick_open_dialog.cpp @@ -709,7 +709,7 @@ void QuickOpenResultContainer::_notification(int p_what) { file_details_path->add_theme_color_override(SceneStringName(font_color), text_color); no_results_label->add_theme_color_override(SceneStringName(font_color), text_color); - panel_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("QuickOpenBackgroundPanel"), EditorStringName(EditorStyles))); + panel_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); if (content_display_mode == QuickOpenDisplayMode::LIST) { display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileThumbnail"))); diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp index 0648327fab..b635816bd9 100644 --- a/editor/plugins/editor_context_menu_plugin.cpp +++ b/editor/plugins/editor_context_menu_plugin.cpp @@ -67,10 +67,21 @@ void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String & context_menu_items.insert(p_name, item); } +void EditorContextMenuPlugin::add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture) { + ERR_FAIL_NULL(p_menu); + + ContextMenuItem item; + item.item_name = p_name; + item.icon = p_texture; + item.submenu = p_menu; + context_menu_items.insert(p_name, item); +} + void EditorContextMenuPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut); ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>())); + ClassDB::bind_method(D_METHOD("add_context_submenu_item", "name", "menu", "icon"), &EditorContextMenuPlugin::add_context_submenu_item, DEFVAL(Ref<Texture2D>())); GDVIRTUAL_BIND(_popup_menu, "paths"); @@ -117,12 +128,17 @@ void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup EditorContextMenuPlugin::ContextMenuItem &item = E.value; item.id = id; - if (item.icon.is_valid()) { - p_popup->add_icon_item(item.icon, item.item_name, id); - p_popup->set_item_icon_max_width(-1, icon_size); + if (item.submenu) { + p_popup->add_submenu_node_item(item.item_name, item.submenu, id); } else { p_popup->add_item(item.item_name, id); } + + if (item.icon.is_valid()) { + p_popup->set_item_icon(-1, item.icon); + p_popup->set_item_icon_max_width(-1, icon_size); + } + if (item.shortcut.is_valid()) { p_popup->set_item_shortcut(-1, item.shortcut, true); } diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h index 0232d254ba..86c67dedda 100644 --- a/editor/plugins/editor_context_menu_plugin.h +++ b/editor/plugins/editor_context_menu_plugin.h @@ -65,6 +65,7 @@ public: Callable callable; Ref<Texture2D> icon; Ref<Shortcut> shortcut; + PopupMenu *submenu = nullptr; }; HashMap<String, ContextMenuItem> context_menu_items; HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts; @@ -80,6 +81,7 @@ public: void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable); void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture); void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture); + void add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture); }; VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index fe7301975f..0cf194b7fe 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -4255,8 +4255,31 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const ray_params.to = world_pos + world_ray * camera->get_far(); PhysicsDirectSpaceState3D::RayResult result; - if (ss->intersect_ray(ray_params, result)) { - return result.position; + if (ss->intersect_ray(ray_params, result) && preview_node->get_child_count() > 0) { + // Calculate an offset for the `preview_node` such that the its bounding box is on top of and touching the contact surface's plane. + + // Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal. + const Vector3 bb_basis_x = result.normal; + Vector3 bb_basis_y = Vector3(0, 1, 0); + bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x); + if (bb_basis_y.is_zero_approx()) { + bb_basis_y = Vector3(0, 0, 1); + bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x); + } + bb_basis_y = bb_basis_y.normalized(); + const Vector3 bb_basis_z = bb_basis_x.cross(bb_basis_y); + const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z); + + // This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible. + const Transform3D bb_transform = Transform3D(bb_basis, preview_node->get_transform().origin); + const AABB preview_node_bb = _calculate_spatial_bounds(preview_node, true, &bb_transform); + // The x-axis's alignment with the surface normal also makes it trivial to get the distance from `preview_node`'s origin at (0, 0, 0) to the correct AABB face. + const float offset_distance = -preview_node_bb.position.x; + + // `result_offset` is in global space. + const Vector3 result_offset = result.position + result.normal * offset_distance; + + return result_offset; } const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; @@ -4284,18 +4307,21 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const return world_pos + world_ray * FALLBACK_DISTANCE; } -AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent) { +AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level, const Transform3D *p_bounds_orientation) { AABB bounds; - if (!p_top_level_parent) { - p_top_level_parent = p_parent; + Transform3D bounds_orientation; + if (p_bounds_orientation) { + bounds_orientation = *p_bounds_orientation; + } else { + bounds_orientation = p_parent->get_global_transform(); } if (!p_parent) { return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); } - Transform3D xform_to_top_level_parent_space = p_top_level_parent->get_global_transform().affine_inverse() * p_parent->get_global_transform(); + const Transform3D xform_to_top_level_parent_space = bounds_orientation.affine_inverse() * p_parent->get_global_transform(); const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent); if (visual_instance) { @@ -4306,9 +4332,9 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, con bounds = xform_to_top_level_parent_space.xform(bounds); for (int i = 0; i < p_parent->get_child_count(); i++) { - Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i)); - if (child) { - AABB child_bounds = _calculate_spatial_bounds(child, p_top_level_parent); + const Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i)); + if (child && !(p_omit_top_level && child->is_set_as_top_level())) { + const AABB child_bounds = _calculate_spatial_bounds(child, p_omit_top_level, &bounds_orientation); bounds.merge_with(child_bounds); } } @@ -4359,6 +4385,10 @@ void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) con if (instance) { instance = _sanitize_preview_node(instance); preview_node->add_child(instance); + Node3D *node_3d = Object::cast_to<Node3D>(instance); + if (node_3d) { + node_3d->set_as_top_level(false); + } } add_preview = true; } @@ -4579,8 +4609,12 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path } Transform3D new_tf = node3d->get_transform(); - new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position()); - new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis; + if (node3d->is_set_as_top_level()) { + new_tf.origin += preview_node_pos; + } else { + new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position()); + new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis; + } undo_redo->add_do_method(instantiated_scene, "set_transform", new_tf); } diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index c7e6420875..a8cade36fd 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -472,7 +472,7 @@ private: Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const; Vector3 _get_instance_position(const Point2 &p_pos) const; - static AABB _calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent = nullptr); + static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level = false, const Transform3D *p_bounds_orientation = nullptr); Node *_sanitize_preview_node(Node *p_node) const; diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 8f646a7621..cc488ff340 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -3596,6 +3596,13 @@ void ThemeEditor::_theme_close_button_cbk() { } } +void ThemeEditor::_scene_closed(const String &p_path) { + if (theme.is_valid() && theme->is_built_in() && theme->get_path().get_slice("::", 0) == p_path) { + theme = Ref<Theme>(); + EditorNode::get_singleton()->hide_unused_editors(plugin); + } +} + void ThemeEditor::_add_preview_button_cbk() { preview_scene_dialog->popup_file_dialog(); } @@ -3679,7 +3686,10 @@ void ThemeEditor::_preview_control_picked(String p_class_name) { void ThemeEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_READY: { + EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ThemeEditor::_scene_closed)); + } break; + case NOTIFICATION_THEME_CHANGED: { preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("ThemeEditorPreviewFG"), EditorStringName(EditorStyles))); preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox(SNAME("ThemeEditorPreviewBG"), EditorStringName(EditorStyles))); @@ -3807,71 +3817,7 @@ void ThemeEditorPlugin::make_visible(bool p_visible) { } bool ThemeEditorPlugin::can_auto_hide() const { - Ref<Theme> edited_theme = theme_editor->theme; - if (edited_theme.is_null()) { - return true; - } - - Ref<Resource> edited_resource = Ref<Resource>(InspectorDock::get_inspector_singleton()->get_next_edited_object()); - if (edited_resource.is_null()) { - return true; - } - - // Don't hide if edited resource used by this theme. - Ref<StyleBox> sbox = edited_resource; - if (sbox.is_valid()) { - List<StringName> type_list; - edited_theme->get_stylebox_type_list(&type_list); - - for (const StringName &E : type_list) { - List<StringName> list; - edited_theme->get_stylebox_list(E, &list); - - for (const StringName &F : list) { - if (edited_theme->get_stylebox(F, E) == sbox) { - return false; - } - } - } - return true; - } - - Ref<Texture2D> tex = edited_resource; - if (tex.is_valid()) { - List<StringName> type_list; - edited_theme->get_icon_type_list(&type_list); - - for (const StringName &E : type_list) { - List<StringName> list; - edited_theme->get_icon_list(E, &list); - - for (const StringName &F : list) { - if (edited_theme->get_icon(F, E) == tex) { - return false; - } - } - } - return true; - } - - Ref<Font> fnt = edited_resource; - if (fnt.is_valid()) { - List<StringName> type_list; - edited_theme->get_font_type_list(&type_list); - - for (const StringName &E : type_list) { - List<StringName> list; - edited_theme->get_font_list(E, &list); - - for (const StringName &F : list) { - if (edited_theme->get_font(F, E) == fnt) { - return false; - } - } - } - return true; - } - return true; + return theme_editor->theme.is_null(); } ThemeEditorPlugin::ThemeEditorPlugin() { diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 1d009637b7..39dc8d154b 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -445,6 +445,7 @@ class ThemeEditor : public VBoxContainer { void _theme_save_button_cbk(bool p_save_as); void _theme_edit_button_cbk(); void _theme_close_button_cbk(); + void _scene_closed(const String &p_path); void _add_preview_button_cbk(); void _preview_scene_dialog_cbk(const String &p_path); diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 12b9761fd2..c6921699a4 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -874,6 +874,11 @@ void GenericTilePolygonEditor::_notification(int p_what) { button_expand->set_pressed_no_signal(false); } } break; + + case NOTIFICATION_READY: { + get_parent()->connect(SceneStringName(tree_exited), callable_mp(TileSetEditor::get_singleton(), &TileSetEditor::remove_expanded_editor)); + } break; + case NOTIFICATION_THEME_CHANGED: { button_expand->set_icon(get_editor_theme_icon(SNAME("DistractionFree"))); button_create->set_icon(get_editor_theme_icon(SNAME("CurveCreate"))); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 418f4e4932..f973367bed 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -104,7 +104,8 @@ void ProjectSettingsEditor::_update_advanced(bool p_is_advanced) { } void ProjectSettingsEditor::_advanced_toggled(bool p_button_pressed) { - EditorSettings::get_singleton()->set_project_metadata("project_settings", "advanced_mode", p_button_pressed); + EditorSettings::get_singleton()->set("_project_settings_advanced_mode", p_button_pressed); + EditorSettings::get_singleton()->save(); _update_advanced(p_button_pressed); } @@ -768,8 +769,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { set_ok_button_text(TTR("Close")); set_hide_on_ok(true); - bool use_advanced = EditorSettings::get_singleton()->get_project_metadata("project_settings", "advanced_mode", false); - + bool use_advanced = EDITOR_DEF("_project_settings_advanced_mode", false); if (use_advanced) { advanced->set_pressed(true); } diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub index 0142317e1e..9bea0a0ca9 100644 --- a/modules/basis_universal/SCsub +++ b/modules/basis_universal/SCsub @@ -50,6 +50,26 @@ if env.dev_build: env_thirdparty = env_basisu.Clone() env_thirdparty.disable_warnings() + +# Disable unneeded features to reduce binary size. +# <https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder> +env_thirdparty.Append( + CPPDEFINES=[ + # Storage formats. + # Godot only implements `.basis` support through basis_universal. + # Support for `.ktx` files are implemented with a direct libktx implementation. + # Building the encoder requires `BASISD_SUPPORT_KTX2` to be enabled, + # so we can only disable Zstandard compression for `.ktx` files + # (this is not used in `.basis` files). + ("BASISD_SUPPORT_KTX2_ZSTD", 0), + # GPU compression formats. + ("BASISD_SUPPORT_ATC", 0), # Proprietary Adreno format not supported by Godot. + ("BASISD_SUPPORT_FXT1", 0), # Legacy format not supported by Godot. + ("BASISD_SUPPORT_PVRTC1", 0), # Legacy format not supported by Godot. + ("BASISD_SUPPORT_PVRTC2", 0), # Legacy format not supported by Godot. + ] +) + if env.editor_build: env_thirdparty.Append(CPPDEFINES=["BASISU_NO_IMG_LOADERS"]) env_thirdparty.add_source_files(thirdparty_obj, encoder_sources) diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 989a7f21f3..20a3a996bc 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -2,7 +2,7 @@ import os import sys from typing import TYPE_CHECKING -from methods import detect_darwin_sdk_path, print_error +from methods import detect_darwin_sdk_path, print_error, print_warning if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment @@ -156,7 +156,7 @@ def configure(env: "SConsEnvironment"): env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) if env["metal"] and env["arch"] != "arm64": - # Only supported on arm64, so skip it for x86_64 builds. + print_warning("Target architecture '{}' does not support the Metal rendering driver".format(env["arch"])) env["metal"] = False if env["metal"]: diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 1d4a944dc4..9e6f191faa 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -96,39 +96,156 @@ <member name="icons/app_store_1024x1024" type="String" setter="" getter=""> App Store application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> - <member name="icons/ipad_76x76" type="String" setter="" getter=""> - Home screen application icon file on iPad (1x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + <member name="icons/app_store_1024x1024_dark" type="String" setter="" getter=""> + App Store application icon file, dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/app_store_1024x1024_tinted" type="String" setter="" getter=""> + App Store application icon file, tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/icon_1024x1024" type="String" setter="" getter=""> + Base application icon used to generate other icons. If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/icon_1024x1024_dark" type="String" setter="" getter=""> + Base application icon used to generate other icons, dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/icon_1024x1024_tinted" type="String" setter="" getter=""> + Base application icon used to generate other icons, tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_128x128" type="String" setter="" getter=""> + iOS application 64x64 icon file (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_128x128_dark" type="String" setter="" getter=""> + iOS application 64x64 icon file (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_128x128_tinted" type="String" setter="" getter=""> + iOS application 64x64 icon file (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_136x136" type="String" setter="" getter=""> + iOS application 68x68 icon file (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_136x136_dark" type="String" setter="" getter=""> + iOS application 68x68 icon file (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_136x136_tinted" type="String" setter="" getter=""> + iOS application 68x68 icon file (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_192x192" type="String" setter="" getter=""> + iOS application 64x64 icon file (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_192x192_dark" type="String" setter="" getter=""> + iOS application 64x64 icon file (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ios_192x192_tinted" type="String" setter="" getter=""> + iOS application 64x64 icon file (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> <member name="icons/ipad_152x152" type="String" setter="" getter=""> Home screen application icon file on iPad (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/ipad_152x152_dark" type="String" setter="" getter=""> + Home screen application icon file on iPad (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ipad_152x152_tinted" type="String" setter="" getter=""> + Home screen application icon file on iPad (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/ipad_167x167" type="String" setter="" getter=""> Home screen application icon file on iPad (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/ipad_167x167_dark" type="String" setter="" getter=""> + Home screen application icon file on iPad (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ipad_167x167_tinted" type="String" setter="" getter=""> + Home screen application icon file on iPad (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/iphone_120x120" type="String" setter="" getter=""> Home screen application icon file on iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/iphone_120x120_dark" type="String" setter="" getter=""> + Home screen application icon file on iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/iphone_120x120_tinted" type="String" setter="" getter=""> + Home screen application icon file on iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/iphone_180x180" type="String" setter="" getter=""> Home screen application icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/iphone_180x180_dark" type="String" setter="" getter=""> + Home screen application icon file on iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/iphone_180x180_tinted" type="String" setter="" getter=""> + Home screen application icon file on iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/notification_40x40" type="String" setter="" getter=""> Notification icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/notification_40x40_dark" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_40x40_tinted" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/notification_60x60" type="String" setter="" getter=""> Notification icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/notification_60x60_dark" type="String" setter="" getter=""> + Notification icon file on iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_60x60_tinted" type="String" setter="" getter=""> + Notification icon file on iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_76x76" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_76x76_dark" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_76x76_tinted" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_114x114" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_114x114_dark" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_114x114_tinted" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/settings_58x58" type="String" setter="" getter=""> Application settings icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/settings_58x58_dark" type="String" setter="" getter=""> + Application settings icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/settings_58x58_tinted" type="String" setter="" getter=""> + Application settings icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="icons/settings_87x87" type="String" setter="" getter=""> Application settings icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> - <member name="icons/spotlight_40x40" type="String" setter="" getter=""> - Spotlight icon file on iPad (1x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + <member name="icons/settings_87x87_dark" type="String" setter="" getter=""> + Application settings icon file on iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/settings_87x87_tinted" type="String" setter="" getter=""> + Application settings icon file on iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> <member name="icons/spotlight_80x80" type="String" setter="" getter=""> Spotlight icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> + <member name="icons/spotlight_80x80_dark" type="String" setter="" getter=""> + Spotlight icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/spotlight_80x80_tinted" type="String" setter="" getter=""> + Spotlight icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/spotlight_120x120" type="String" setter="" getter=""> + Spotlight icon file on iPad and iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/spotlight_120x120_dark" type="String" setter="" getter=""> + Spotlight icon file on iPad and iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/spotlight_120x120_tinted" type="String" setter="" getter=""> + Spotlight icon file on iPad and iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> <member name="privacy/active_keyboard_access_reasons" type="int" setter="" getter=""> The reasons your app use active keyboard API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url]. </member> diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index b99e825540..d6cd2e0f3c 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -79,33 +79,37 @@ struct IconInfo { }; static const IconInfo icon_infos[] = { - // Home screen on iPhone - { PNAME("icons/iphone_120x120"), "iphone", "Icon-120.png", "120", "2x", "60x60", false }, - { PNAME("icons/iphone_120x120"), "iphone", "Icon-120.png", "120", "3x", "40x40", false }, - { PNAME("icons/iphone_180x180"), "iphone", "Icon-180.png", "180", "3x", "60x60", false }, + // Settings on iPhone, iPad Pro, iPad, iPad mini + { PNAME("icons/settings_58x58"), "universal", "Icon-58", "58", "2x", "29x29", false }, + { PNAME("icons/settings_87x87"), "universal", "Icon-87", "87", "3x", "29x29", false }, - // Home screen on iPad - { PNAME("icons/ipad_76x76"), "ipad", "Icon-76.png", "76", "1x", "76x76", false }, - { PNAME("icons/ipad_152x152"), "ipad", "Icon-152.png", "152", "2x", "76x76", false }, - { PNAME("icons/ipad_167x167"), "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false }, + // Notifications on iPhone, iPad Pro, iPad, iPad mini + { PNAME("icons/notification_40x40"), "universal", "Icon-40", "40", "2x", "20x20", false }, + { PNAME("icons/notification_60x60"), "universal", "Icon-60", "60", "3x", "20x20", false }, + { PNAME("icons/notification_76x76"), "universal", "Icon-76", "76", "2x", "38x38", false }, + { PNAME("icons/notification_114x114"), "universal", "Icon-114", "114", "3x", "38x38", false }, + + // Spotlight on iPhone, iPad Pro, iPad, iPad mini + { PNAME("icons/spotlight_80x80"), "universal", "Icon-80", "80", "2x", "40x40", false }, + { PNAME("icons/spotlight_120x120"), "universal", "Icon-120", "120", "3x", "40x40", false }, + + // Home Screen on iPhone + { PNAME("icons/iphone_120x120"), "universal", "Icon-120-1", "120", "2x", "60x60", false }, + { PNAME("icons/iphone_180x180"), "universal", "Icon-180", "180", "3x", "60x60", false }, + + // Home Screen on iPad Pro + { PNAME("icons/ipad_167x167"), "universal", "Icon-167", "167", "2x", "83.5x83.5", false }, + + // Home Screen on iPad, iPad mini + { PNAME("icons/ipad_152x152"), "universal", "Icon-152", "152", "2x", "76x76", false }, + + { PNAME("icons/ios_128x128"), "universal", "Icon-128", "128", "2x", "64x64", false }, + { PNAME("icons/ios_192x192"), "universal", "Icon-192", "192", "3x", "64x64", false }, + + { PNAME("icons/ios_136x136"), "universal", "Icon-136", "136", "2x", "68x68", false }, // App Store - { PNAME("icons/app_store_1024x1024"), "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true }, - - // Spotlight - { PNAME("icons/spotlight_40x40"), "ipad", "Icon-40.png", "40", "1x", "40x40", false }, - { PNAME("icons/spotlight_80x80"), "iphone", "Icon-80.png", "80", "2x", "40x40", false }, - { PNAME("icons/spotlight_80x80"), "ipad", "Icon-80.png", "80", "2x", "40x40", false }, - - // Settings - { PNAME("icons/settings_58x58"), "iphone", "Icon-58.png", "58", "2x", "29x29", false }, - { PNAME("icons/settings_58x58"), "ipad", "Icon-58.png", "58", "2x", "29x29", false }, - { PNAME("icons/settings_87x87"), "iphone", "Icon-87.png", "87", "3x", "29x29", false }, - - // Notification - { PNAME("icons/notification_40x40"), "iphone", "Icon-40.png", "40", "2x", "20x20", false }, - { PNAME("icons/notification_40x40"), "ipad", "Icon-40.png", "40", "2x", "20x20", false }, - { PNAME("icons/notification_60x60"), "iphone", "Icon-60.png", "60", "3x", "20x20", false } + { PNAME("icons/app_store_1024x1024"), "universal", "Icon-1024", "1024", "1x", "1024x1024", true }, }; struct APIAccessInfo { @@ -250,7 +254,7 @@ bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPre } bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); - if (p_option.begins_with("privacy") || p_option == "application/generate_simulator_library_if_missing") { + if (p_option.begins_with("privacy") || p_option == "application/generate_simulator_library_if_missing" || (p_option.begins_with("icons/") && !p_option.begins_with("icons/icon") && !p_option.begins_with("icons/app_store"))) { return advanced_options_enabled; } @@ -368,11 +372,17 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) } } + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_dark", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_tinted", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), "")); + HashSet<String> used_names; for (uint64_t i = 0; i < sizeof(icon_infos) / sizeof(icon_infos[0]); ++i) { if (!used_names.has(icon_infos[i].preset_key)) { used_names.insert(icon_infos[i].preset_key); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, icon_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key), PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_dark", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_tinted", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); } } r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0)); @@ -883,72 +893,127 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr Color boot_bg_color = GLOBAL_GET("application/boot_splash/bg_color"); + enum IconColorMode { + ICON_NORMAL, + ICON_DARK, + ICON_TINTED, + ICON_MAX, + }; + + bool first_icon = true; for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { - IconInfo info = icon_infos[i]; - int side_size = String(info.actual_size_side).to_int(); - String icon_path = p_preset->get(info.preset_key); - if (icon_path.length() == 0) { - // Resize main app icon - icon_path = GLOBAL_GET("application/config/icon"); - Ref<Image> img = memnew(Image); - Error err = ImageLoader::load_image(icon_path, img); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path)); - return ERR_UNCONFIGURED; - } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key)); - img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); - Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8); - new_img->fill(boot_bg_color); - _blend_and_rotate(new_img, img, false); - err = new_img->save_png(p_iconset_dir + info.export_name); - } else { - img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); - err = img->save_png(p_iconset_dir + info.export_name); + for (int color_mode = ICON_NORMAL; color_mode < ICON_MAX; color_mode++) { + IconInfo info = icon_infos[i]; + int side_size = String(info.actual_size_side).to_int(); + String key = info.preset_key; + String exp_name = info.export_name; + if (color_mode == ICON_DARK) { + key += "_dark"; + exp_name += "_dark"; + } else if (color_mode == ICON_TINTED) { + key += "_tinted"; + exp_name += "_tinted"; + } + exp_name += ".png"; + String icon_path = p_preset->get(key); + bool resize_waning = true; + if (icon_path.is_empty()) { + // Load and resize base icon. + key = "icons/icon_1024x1024"; + if (color_mode == ICON_DARK) { + key += "_dark"; + } else if (color_mode == ICON_TINTED) { + key += "_tinted"; + } + icon_path = p_preset->get(key); + resize_waning = false; } - if (err) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path)); - return err; + if (icon_path.is_empty()) { + if (color_mode != ICON_NORMAL) { + continue; + } + // Resize main app icon. + icon_path = GLOBAL_GET("application/config/icon"); + Ref<Image> img = memnew(Image); + Error err = ImageLoader::load_image(icon_path, img); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path)); + return ERR_UNCONFIGURED; + } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) { + img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); + Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8); + new_img->fill(boot_bg_color); + _blend_and_rotate(new_img, img, false); + err = new_img->save_png(p_iconset_dir + exp_name); + } else { + img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); + err = img->save_png(p_iconset_dir + exp_name); + } + if (err) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path)); + return err; + } + } else { + // Load custom icon and resize if required. + Ref<Image> img = memnew(Image); + Error err = ImageLoader::load_image(icon_path, img); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path)); + return ERR_UNCONFIGURED; + } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) { + if (resize_waning) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key)); + } + img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); + Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8); + new_img->fill(boot_bg_color); + _blend_and_rotate(new_img, img, false); + err = new_img->save_png(p_iconset_dir + exp_name); + } else if (img->get_width() != side_size || img->get_height() != side_size) { + if (resize_waning) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(side_size, side_size))); + } + img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); + err = img->save_png(p_iconset_dir + exp_name); + } else if (!icon_path.ends_with(".png")) { + err = img->save_png(p_iconset_dir + exp_name); + } else { + err = da->copy(icon_path, p_iconset_dir + exp_name); + } + + if (err) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path)); + return err; + } } - } else { - // Load custom icon and resize if required - Ref<Image> img = memnew(Image); - Error err = ImageLoader::load_image(icon_path, img); - if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path)); - return ERR_UNCONFIGURED; - } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key)); - img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); - Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8); - new_img->fill(boot_bg_color); - _blend_and_rotate(new_img, img, false); - err = new_img->save_png(p_iconset_dir + info.export_name); - } else if (img->get_width() != side_size || img->get_height() != side_size) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(side_size, side_size))); - img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); - err = img->save_png(p_iconset_dir + info.export_name); + sizes += String(info.actual_size_side) + "\n"; + if (first_icon) { + first_icon = false; } else { - err = da->copy(icon_path, p_iconset_dir + info.export_name); + json_description += ","; + } + json_description += String("{"); + if (color_mode != ICON_NORMAL) { + json_description += String("\"appearances\":[{"); + json_description += String("\"appearance\":\"luminosity\","); + if (color_mode == ICON_DARK) { + json_description += String("\"value\":\"dark\""); + } else if (color_mode == ICON_TINTED) { + json_description += String("\"value\":\"tinted\""); + } + json_description += String("}],"); } - - if (err) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path)); - return err; + json_description += String("\"idiom\":") + "\"" + info.idiom + "\","; + json_description += String("\"platform\":\"ios\","); + json_description += String("\"size\":") + "\"" + info.unscaled_size + "\","; + if (String(info.scale) != "1x") { + json_description += String("\"scale\":") + "\"" + info.scale + "\","; } + json_description += String("\"filename\":") + "\"" + exp_name + "\""; + json_description += String("}"); } - sizes += String(info.actual_size_side) + "\n"; - if (i > 0) { - json_description += ","; - } - json_description += String("{"); - json_description += String("\"idiom\":") + "\"" + info.idiom + "\","; - json_description += String("\"size\":") + "\"" + info.unscaled_size + "\","; - json_description += String("\"scale\":") + "\"" + info.scale + "\","; - json_description += String("\"filename\":") + "\"" + info.export_name + "\""; - json_description += String("}"); } - json_description += "]}"; + json_description += "],\"info\":{\"author\":\"xcode\",\"version\":1}}"; Ref<FileAccess> json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE); if (json_file.is_null()) { diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 595a83ead3..a8968b592e 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -2,7 +2,7 @@ import os import sys from typing import TYPE_CHECKING -from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error +from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error, print_warning from platform_methods import detect_arch, detect_mvk if TYPE_CHECKING: @@ -249,7 +249,7 @@ def configure(env: "SConsEnvironment"): env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"]) if env["metal"] and env["arch"] != "arm64": - # Only supported on arm64, so skip it for x86_64 builds. + print_warning("Target architecture '{}' does not support the Metal rendering driver".format(env["arch"])) env["metal"] = False extra_frameworks = set() diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 5d8e89938c..ffa3840181 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1520,6 +1520,7 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod rendering_device->screen_create(window_id); } #endif + wd.initialized = true; return window_id; } @@ -2065,7 +2066,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window) return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { // Windows docs for window styles: // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles @@ -2074,12 +2075,16 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { r_style_ex |= WS_EX_APPWINDOW; - r_style |= WS_VISIBLE; + if (p_initialized) { + r_style |= WS_VISIBLE; + } } if (p_fullscreen || p_borderless) { r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. - if (p_maximized) { + if (p_minimized) { + r_style |= WS_MINIMIZE; + } else if (p_maximized) { r_style |= WS_MAXIMIZE; } if (!p_fullscreen) { @@ -2094,13 +2099,19 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre } } else { if (p_resizable) { - if (p_maximized) { + if (p_minimized) { + r_style = WS_OVERLAPPEDWINDOW | WS_MINIMIZE; + } else if (p_maximized) { r_style = WS_OVERLAPPEDWINDOW | WS_MAXIMIZE; } else { r_style = WS_OVERLAPPEDWINDOW; } } else { - r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + if (p_minimized) { + r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MINIMIZE; + } else { + r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + } } } @@ -2108,7 +2119,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE; } - if (!p_borderless && !p_no_activate_focus) { + if (!p_borderless && !p_no_activate_focus && p_initialized) { r_style |= WS_VISIBLE; } @@ -2125,7 +2136,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain DWORD style = 0; DWORD style_ex = 0; - _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.initialized, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.minimized, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); @@ -5536,7 +5547,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, false, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MINIMIZED, p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); RECT WindowRect; @@ -6368,6 +6379,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } + windows[MAIN_WINDOW_ID].initialized = true; show_window(MAIN_WINDOW_ID); #if defined(RD_ENABLED) diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 54e1c9681d..7d6a3e96a6 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -524,6 +524,8 @@ class DisplayServerWindows : public DisplayServer { bool is_popup = false; Rect2i parent_safe_rect; + + bool initialized = false; }; JoypadWindows *joypad = nullptr; @@ -591,7 +593,7 @@ class DisplayServerWindows : public DisplayServer { HashMap<int64_t, Vector2> pointer_last_pos; void _send_window_event(const WindowData &wd, WindowEvent p_event); - void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + void _get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 002a738b83..fe4c91cb56 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -245,7 +245,21 @@ void ColorPicker::finish_shaders() { } void ColorPicker::set_focus_on_line_edit() { - callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + bool has_hardware_keyboard = true; +#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) + has_hardware_keyboard = DisplayServer::get_singleton()->has_hardware_keyboard(); +#endif // ANDROID_ENABLED || IOS_ENABLED + if (has_hardware_keyboard) { + callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + } else { + // A hack to avoid showing the virtual keyboard when the ColorPicker window popups and + // no hardware keyboard is detected on Android and IOS. + // This will only focus the LineEdit without editing, the virtual keyboard will only be visible when + // we touch the LineEdit to enter edit mode. + callable_mp(c_text, &LineEdit::set_editable).call_deferred(false); + callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + callable_mp(c_text, &LineEdit::set_editable).call_deferred(true); + } } void ColorPicker::_update_controls() { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 9040693a6d..646757008a 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -653,7 +653,9 @@ void GraphEdit::remove_child_notify(Node *p_child) { minimap = nullptr; } else if (p_child == connections_layer) { connections_layer = nullptr; - WARN_PRINT("GraphEdit's connection_layer removed. This should not be done. If you like to remove all GraphElements from a GraphEdit node, do not simply remove all non-internal children but check their type since the connection layer has to be kept non-internal due to technical reasons."); + if (is_inside_tree()) { + WARN_PRINT("GraphEdit's connection_layer removed. This should not be done. If you like to remove all GraphElements from a GraphEdit node, do not simply remove all non-internal children but check their type since the connection layer has to be kept non-internal due to technical reasons."); + } } if (top_layer != nullptr && is_inside_tree()) { diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 6e5b555cdf..9967805134 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -45,28 +45,37 @@ #include "editor/editor_settings.h" #endif -void LineEdit::_edit() { +void LineEdit::edit() { if (!is_inside_tree()) { return; } if (!has_focus()) { grab_focus(); + return; } if (!editable || editing) { return; } + if (select_all_on_focus) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + // Select all when the mouse button is up. + pending_select_all_on_focus = true; + } else { + select_all(); + } + } + editing = true; _validate_caret_can_draw(); show_virtual_keyboard(); queue_redraw(); - emit_signal(SNAME("editing_toggled"), true); } -void LineEdit::_unedit() { +void LineEdit::unedit() { if (!editing) { return; } @@ -84,8 +93,6 @@ void LineEdit::_unedit() { if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { deselect(); } - - emit_signal(SNAME("editing_toggled"), false); } bool LineEdit::is_editing() const { @@ -390,7 +397,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editable && !editing) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); } accept_event(); @@ -406,7 +414,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { set_caret_at_pixel_pos(b->get_position().x); if (!editing) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); } if (!paste_buffer.is_empty()) { @@ -506,7 +515,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editable && !editing) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); return; } queue_redraw(); @@ -599,7 +609,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); + accept_event(); return; } @@ -734,7 +746,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } accept_event(); @@ -743,7 +756,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (k->is_action("ui_cancel")) { if (editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } accept_event(); @@ -1332,24 +1346,17 @@ void LineEdit::_notification(int p_what) { } break; case NOTIFICATION_FOCUS_ENTER: { - if (select_all_on_focus) { - if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - // Select all when the mouse button is up. - pending_select_all_on_focus = true; - } else { - select_all(); - } - } - // Only allow editing if the LineEdit is not focused with arrow keys. if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); } } break; case NOTIFICATION_FOCUS_EXIT: { if (editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } } break; @@ -2138,7 +2145,8 @@ void LineEdit::set_editable(bool p_editable) { editable = p_editable; if (!editable && editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } _validate_caret_can_draw(); @@ -2759,6 +2767,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("edit"), &LineEdit::edit); + ClassDB::bind_method(D_METHOD("unedit"), &LineEdit::unedit); ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index ac7436646b..9253dd8711 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -207,9 +207,6 @@ private: float base_scale = 1.0; } theme_cache; - void _edit(); - void _unedit(); - void _close_ime_window(); void _update_ime_window_position(); @@ -265,6 +262,8 @@ protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; public: + void edit(); + void unedit(); bool is_editing() const; bool has_ime_text() const; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 2211bd76fc..1ac0e8b59f 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -547,7 +547,7 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const { int found = 0; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 8238d54381..646cd9c70e 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -4034,25 +4034,25 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::WHEEL_UP: { - if (_scroll(false, -mb->get_factor() / 8)) { + if (_scroll(mb->is_shift_pressed(), -mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_DOWN: { - if (_scroll(false, mb->get_factor() / 8)) { + if (_scroll(mb->is_shift_pressed(), mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_LEFT: { - if (_scroll(true, -mb->get_factor() / 8)) { + if (_scroll(!mb->is_shift_pressed(), -mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_RIGHT: { - if (_scroll(true, mb->get_factor() / 8)) { + if (_scroll(!mb->is_shift_pressed(), mb->get_factor() / 8)) { accept_event(); } diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 106130872d..71d91b970e 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -302,11 +302,15 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } + Node *node = gr_nodes[i]; if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; - gr_nodes[i]->callp(p_function, p_args, p_argcount, ce); + node->callp(p_function, p_args, p_argcount, ce); + if (unlikely(ce.error != Callable::CallError::CALL_OK && ce.error != Callable::CallError::CALL_ERROR_INVALID_METHOD)) { + ERR_PRINT(vformat("Error calling group method on node \"%s\": %s.", node->get_name(), Variant::get_callable_error_text(Callable(node, p_function), p_args, p_argcount, ce))); + } } else { - MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount); + MessageQueue::get_singleton()->push_callp(node, p_function, p_args, p_argcount); } } @@ -316,11 +320,15 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } + Node *node = gr_nodes[i]; if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; - gr_nodes[i]->callp(p_function, p_args, p_argcount, ce); + node->callp(p_function, p_args, p_argcount, ce); + if (unlikely(ce.error != Callable::CallError::CALL_OK && ce.error != Callable::CallError::CALL_ERROR_INVALID_METHOD)) { + ERR_PRINT(vformat("Error calling group method on node \"%s\": %s.", node->get_name(), Variant::get_callable_error_text(Callable(node, p_function), p_args, p_argcount, ce))); + } } else { - MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount); + MessageQueue::get_singleton()->push_callp(node, p_function, p_args, p_argcount); } } } diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index d531eea311..e234a81c88 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -612,29 +612,27 @@ Error ResourceLoaderText::load() { } } - if (ClassDB::has_property(res->get_class_name(), assign)) { - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = res->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); - } + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); } } + } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary set_dict = value; - bool is_get_valid = false; - Variant get_value = res->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { - Dictionary get_dict = get_value; - if (!set_dict.is_same_typed(get_dict)) { - value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), - get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); - } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } } } @@ -754,29 +752,27 @@ Error ResourceLoaderText::load() { } } - if (ClassDB::has_property(resource->get_class_name(), assign)) { - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = resource->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); - } + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); } } + } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary set_dict = value; - bool is_get_valid = false; - Variant get_value = resource->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { - Dictionary get_dict = get_value; - if (!set_dict.is_same_typed(get_dict)) { - value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), - get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); - } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } } } @@ -1708,6 +1704,8 @@ static String _resource_get_class(Ref<Resource> p_resource) { } Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { + Resource::seed_scene_unique_id(p_path.hash()); // Seeding for save path should make it deterministic for importers. + if (p_path.ends_with(".tscn")) { packed_scene = p_resource; } diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 28958f0393..979f590c4c 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1446,6 +1446,9 @@ void RendererCanvasRenderRD::CanvasShaderData::_create_pipeline(PipelineKey p_pi blend_state.attachments.push_back(attachment); + RD::PipelineMultisampleState multisample_state; + multisample_state.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_pipeline_key.framebuffer_format_id, 0); + // Convert the specialization from the key to pipeline specialization constants. Vector<RD::PipelineSpecializationConstant> specialization_constants; RD::PipelineSpecializationConstant sc; @@ -1457,7 +1460,7 @@ void RendererCanvasRenderRD::CanvasShaderData::_create_pipeline(PipelineKey p_pi RID shader_rid = get_shader(p_pipeline_key.variant, p_pipeline_key.ubershader); ERR_FAIL_COND(shader_rid.is_null()); - RID pipeline = RD::get_singleton()->render_pipeline_create(shader_rid, p_pipeline_key.framebuffer_format_id, p_pipeline_key.vertex_format_id, p_pipeline_key.render_primitive, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state, dynamic_state_flags, 0, specialization_constants); + RID pipeline = RD::get_singleton()->render_pipeline_create(shader_rid, p_pipeline_key.framebuffer_format_id, p_pipeline_key.vertex_format_id, p_pipeline_key.render_primitive, RD::PipelineRasterizationState(), multisample_state, RD::PipelineDepthStencilState(), blend_state, dynamic_state_flags, 0, specialization_constants); ERR_FAIL_COND(pipeline.is_null()); pipeline_hash_map.add_compiled_pipeline(p_pipeline_key.hash(), pipeline); |