diff options
75 files changed, 1021 insertions, 619 deletions
diff --git a/SConstruct b/SConstruct index 01f1ae638e..28395227d2 100644 --- a/SConstruct +++ b/SConstruct @@ -103,11 +103,13 @@ custom_tools = ["default"] platform_arg = ARGUMENTS.get("platform", ARGUMENTS.get("p", False)) -if os.name == "nt" and (platform_arg == "android" or methods.get_cmdline_bool("use_mingw", False)): - custom_tools = ["mingw"] +if platform_arg == "android": + custom_tools = ["clang", "clang++", "as", "ar", "link"] elif platform_arg == "javascript": # Use generic POSIX build toolchain for Emscripten. custom_tools = ["cc", "c++", "ar", "link", "textfile", "zip"] +elif os.name == "nt" and methods.get_cmdline_bool("use_mingw", False): + custom_tools = ["mingw"] # We let SCons build its default ENV as it includes OS-specific things which we don't # want to have to pull in manually. @@ -395,10 +397,7 @@ if selected_platform in platform_list: sys.path.insert(0, tmppath) import detect - if "create" in dir(detect): - env = detect.create(env_base) - else: - env = env_base.Clone() + env = env_base.Clone() if env["compiledb"]: # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 1f46223a1d..67aa2bbbfb 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -613,6 +613,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_NONE); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index ed7228d0b9..fec5ca5c7b 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -52,41 +52,36 @@ void Resource::set_path(const String &p_path, bool p_take_over) { return; } + if (p_path.is_empty()) { + p_take_over = false; // Can't take over an empty path + } + + ResourceCache::lock.lock(); + if (!path_cache.is_empty()) { - ResourceCache::lock.write_lock(); ResourceCache::resources.erase(path_cache); - ResourceCache::lock.write_unlock(); } path_cache = ""; - ResourceCache::lock.read_lock(); - bool has_path = ResourceCache::resources.has(p_path); - ResourceCache::lock.read_unlock(); + Ref<Resource> existing = ResourceCache::get_ref(p_path); - if (has_path) { + if (existing.is_valid()) { if (p_take_over) { - ResourceCache::lock.write_lock(); - Resource **res = ResourceCache::resources.getptr(p_path); - if (res) { - (*res)->set_name(""); - } - ResourceCache::lock.write_unlock(); + existing->path_cache = String(); + ResourceCache::resources.erase(p_path); } else { - ResourceCache::lock.read_lock(); - bool exists = ResourceCache::resources.has(p_path); - ResourceCache::lock.read_unlock(); - - ERR_FAIL_COND_MSG(exists, "Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + ResourceCache::lock.unlock(); + ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); } } + path_cache = p_path; if (!path_cache.is_empty()) { - ResourceCache::lock.write_lock(); ResourceCache::resources[path_cache] = this; - ResourceCache::lock.write_unlock(); } + ResourceCache::lock.unlock(); _resource_path_changed(); } @@ -380,7 +375,7 @@ void Resource::set_as_translation_remapped(bool p_remapped) { return; } - ResourceCache::lock.write_lock(); + ResourceCache::lock.lock(); if (p_remapped) { ResourceLoader::remapped_list.add(&remapped_list); @@ -388,7 +383,7 @@ void Resource::set_as_translation_remapped(bool p_remapped) { ResourceLoader::remapped_list.remove(&remapped_list); } - ResourceCache::lock.write_unlock(); + ResourceCache::lock.unlock(); } bool Resource::is_translation_remapped() const { @@ -455,9 +450,9 @@ Resource::Resource() : Resource::~Resource() { if (!path_cache.is_empty()) { - ResourceCache::lock.write_lock(); + ResourceCache::lock.lock(); ResourceCache::resources.erase(path_cache); - ResourceCache::lock.write_unlock(); + ResourceCache::lock.unlock(); } if (owners.size()) { WARN_PRINT("Resource is still owned."); @@ -469,7 +464,7 @@ HashMap<String, Resource *> ResourceCache::resources; HashMap<String, HashMap<String, String>> ResourceCache::resource_path_cache; #endif -RWLock ResourceCache::lock; +Mutex ResourceCache::lock; #ifdef TOOLS_ENABLED RWLock ResourceCache::path_cache_lock; #endif @@ -491,46 +486,67 @@ void ResourceCache::reload_externals() { } bool ResourceCache::has(const String &p_path) { - lock.read_lock(); - bool b = resources.has(p_path); - lock.read_unlock(); + lock.lock(); + + Resource **res = resources.getptr(p_path); - return b; + if (res && (*res)->reference_get_count() == 0) { + // This resource is in the process of being deleted, ignore its existence. + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } + + lock.unlock(); + + if (!res) { + return false; + } + + return true; } -Resource *ResourceCache::get(const String &p_path) { - lock.read_lock(); +Ref<Resource> ResourceCache::get_ref(const String &p_path) { + Ref<Resource> ref; + lock.lock(); Resource **res = resources.getptr(p_path); - lock.read_unlock(); + if (res) { + ref = Ref<Resource>(*res); + } - if (!res) { - return nullptr; + if (res && !ref.is_valid()) { + // This resource is in the process of being deleted, ignore its existence + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; } - return *res; + lock.unlock(); + + return ref; } void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { - lock.read_lock(); + lock.lock(); for (KeyValue<String, Resource *> &E : resources) { p_resources->push_back(Ref<Resource>(E.value)); } - lock.read_unlock(); + lock.unlock(); } int ResourceCache::get_cached_resource_count() { - lock.read_lock(); + lock.lock(); int rc = resources.size(); - lock.read_unlock(); + lock.unlock(); return rc; } void ResourceCache::dump(const char *p_file, bool p_short) { #ifdef DEBUG_ENABLED - lock.read_lock(); + lock.lock(); HashMap<String, int> type_count; @@ -562,7 +578,7 @@ void ResourceCache::dump(const char *p_file, bool p_short) { } } - lock.read_unlock(); + lock.unlock(); #else WARN_PRINT("ResourceCache::dump only with in debug builds."); #endif diff --git a/core/io/resource.h b/core/io/resource.h index a45bc6e1e4..a2cde87990 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -153,7 +153,7 @@ public: class ResourceCache { friend class Resource; friend class ResourceLoader; //need the lock - static RWLock lock; + static Mutex lock; static HashMap<String, Resource *> resources; #ifdef TOOLS_ENABLED static HashMap<String, HashMap<String, String>> resource_path_cache; // Each tscn has a set of resource paths and IDs. @@ -166,7 +166,7 @@ class ResourceCache { public: static void reload_externals(); static bool has(const String &p_path); - static Resource *get(const String &p_path); + static Ref<Resource> get_ref(const String &p_path); static void dump(const char *p_file = nullptr, bool p_short = false); static void get_cached_resources(List<Ref<Resource>> *p_resources); static int get_cached_resource_count(); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 2469e1a4be..b1c50e829c 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -693,7 +693,7 @@ Error ResourceLoaderBinary::load() { } if (cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE && ResourceCache::has(path)) { - Ref<Resource> cached = ResourceCache::get(path); + Ref<Resource> cached = ResourceCache::get_ref(path); if (cached.is_valid()) { //already loaded, don't do anything error = OK; @@ -717,10 +717,10 @@ Error ResourceLoaderBinary::load() { if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { //use the existing one - Resource *r = ResourceCache::get(path); - if (r->get_class() == t) { - r->reset_state(); - res = Ref<Resource>(r); + Ref<Resource> cached = ResourceCache::get_ref(path); + if (cached->get_class() == t) { + cached->reset_state(); + res = cached; } } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index fb21db1a19..2cd455475c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -335,23 +335,15 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & thread_load_mutex->unlock(); ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?"); } - //lock first if possible - ResourceCache::lock.read_lock(); - - //get ptr - Resource **rptr = ResourceCache::resources.getptr(local_path); - - if (rptr) { - Ref<Resource> res(*rptr); - //it is possible this resource was just freed in a thread. If so, this referencing will not work and resource is considered not cached - if (res.is_valid()) { - //referencing is fine - load_task.resource = res; - load_task.status = THREAD_LOAD_LOADED; - load_task.progress = 1.0; - } + + Ref<Resource> existing = ResourceCache::get_ref(local_path); + + if (existing.is_valid()) { + //referencing is fine + load_task.resource = existing; + load_task.status = THREAD_LOAD_LOADED; + load_task.progress = 1.0; } - ResourceCache::lock.read_unlock(); } if (!p_source_resource.is_empty()) { @@ -530,27 +522,18 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi } //Is it cached? - ResourceCache::lock.read_lock(); - - Resource **rptr = ResourceCache::resources.getptr(local_path); - if (rptr) { - Ref<Resource> res(*rptr); + Ref<Resource> existing = ResourceCache::get_ref(local_path); - //it is possible this resource was just freed in a thread. If so, this referencing will not work and resource is considered not cached - if (res.is_valid()) { - ResourceCache::lock.read_unlock(); - thread_load_mutex->unlock(); - - if (r_error) { - *r_error = OK; - } + if (existing.is_valid()) { + thread_load_mutex->unlock(); - return res; //use cached + if (r_error) { + *r_error = OK; } - } - ResourceCache::lock.read_unlock(); + return existing; //use cached + } //load using task (but this thread) ThreadLoadTask load_task; @@ -867,7 +850,7 @@ String ResourceLoader::path_remap(const String &p_path) { } void ResourceLoader::reload_translation_remaps() { - ResourceCache::lock.read_lock(); + ResourceCache::lock.lock(); List<Resource *> to_reload; SelfList<Resource> *E = remapped_list.first(); @@ -877,7 +860,7 @@ void ResourceLoader::reload_translation_remaps() { E = E->next(); } - ResourceCache::lock.read_unlock(); + ResourceCache::lock.unlock(); //now just make sure to not delete any of these resources while changing locale.. while (to_reload.front()) { diff --git a/core/object/object.h b/core/object/object.h index fe9231cfd8..7dac96bc2b 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -47,7 +47,7 @@ enum PropertyHint { PROPERTY_HINT_NONE, ///< no hint provided. - PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_lesser][,noslider][,radians][,degrees][,exp][,suffix:<keyword>] range. + PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_lesser][,no_slider][,radians][,degrees][,exp][,suffix:<keyword>] range. PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "full" to also include in/out. (ie: "attenuation,inout") @@ -91,6 +91,7 @@ enum PropertyHint { PROPERTY_HINT_INT_IS_POINTER, PROPERTY_HINT_LOCALE_ID, PROPERTY_HINT_LOCALIZABLE_STRING, + PROPERTY_HINT_NODE_TYPE, ///< a node object type PROPERTY_HINT_MAX, // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit }; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 4048b483e8..863ea899ee 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2460,7 +2460,7 @@ </constant> <constant name="PROPERTY_HINT_RANGE" value="1" enum="PropertyHint"> Hints that an integer or float property should be within a range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"or_greater"[/code] and/or [code]"or_lesser"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"-360,360,1,or_greater,or_lesser"[/code]. - Additionally, other keywords can be included: "exp" for exponential range editing, "radians" for editing radian angles in degrees, "degrees" to hint at an angle and "noslider" to hide the slider. + Additionally, other keywords can be included: "exp" for exponential range editing, "radians" for editing radian angles in degrees, "degrees" to hint at an angle and "no_slider" to hide the slider. </constant> <constant name="PROPERTY_HINT_ENUM" value="2" enum="PropertyHint"> Hints that an integer, float or string property is an enumerated value to pick in a list specified via a hint string. @@ -2583,7 +2583,9 @@ <constant name="PROPERTY_HINT_LOCALIZABLE_STRING" value="44" enum="PropertyHint"> Hints that a dictionary property is string translation map. Dictionary keys are locale codes and, values are translated strings. </constant> - <constant name="PROPERTY_HINT_MAX" value="45" enum="PropertyHint"> + <constant name="PROPERTY_HINT_NODE_TYPE" value="45" enum="PropertyHint"> + </constant> + <constant name="PROPERTY_HINT_MAX" value="46" enum="PropertyHint"> </constant> <constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags"> </constant> diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index c428233372..84f8523da3 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -38,7 +38,7 @@ Gets the edited object. </description> </method> - <method name="get_edited_property"> + <method name="get_edited_property" qualifiers="const"> <return type="StringName" /> <description> Gets the edited property. If your editor is for a single property (added via [method EditorInspectorPlugin._parse_property]), then this will return the property. diff --git a/doc/classes/MovieWriter.xml b/doc/classes/MovieWriter.xml index 9c713bd7ae..bc702adde6 100644 --- a/doc/classes/MovieWriter.xml +++ b/doc/classes/MovieWriter.xml @@ -6,8 +6,8 @@ <description> Godot can record videos with non-real-time simulation. Like the [code]--fixed-fps[/code] command line argument, this forces the reported [code]delta[/code] in [method Node._process] functions to be identical across frames, regardless of how long it actually took to render the frame. This can be used to record high-quality videos with perfect frame pacing regardless of your hardware's capabilities. Godot has 2 built-in [MovieWriter]s: - - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. AVI output is currently limited to a file of 4 GB in size at most. - - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported. + - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. MJPEG does not support transparency. AVI output is currently limited to a file of 4 GB in size at most. + - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported, even if the root viewport is set to be transparent. If you need to encode to a different format or pipe a stream through third-party software, you can extend the [MovieWriter] class to create your own movie writers. This should typically be done using GDExtension for performance reasons. [b]Editor usage:[/b] A default movie file path can be specified in [member ProjectSettings.editor/movie_writer/movie_file]. Alternatively, for running single scenes, a [code]movie_path[/code] metadata can be added to the root node, specifying the path to a movie file that will be used when recording that scene. Once a path is set, click the video reel icon in the top-right corner of the editor to enable Movie Maker mode, then run any scene as usual. The engine will start recording as soon as the splash screen is finished, and it will only stop recording when the engine quits. Click the video reel icon again to disable Movie Maker mode. Note that toggling Movie Maker mode does not affect project instances that are already running. [b]Note:[/b] MovieWriter is available for use in both the editor and exported projects, but it is [i]not[/i] designed for use by end users to record videos while playing. Players wishing to record gameplay videos should install tools such as [url=https://obsproject.com/]OBS Studio[/url] or [url=https://www.maartenbaert.be/simplescreenrecorder/]SimpleScreenRecorder[/url] instead. diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index 757b635252..058f1032cb 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -136,7 +136,8 @@ The maximum distance the agent is allowed away from the ideal path to the final location. This can happen due to trying to avoid collisions. When the maximum distance is exceeded, it recalculates the ideal path. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="10.0"> - The radius of the agent. + The radius of the avoidance agent. This is the "body" of the avoidance agent and not the avoidance maneuver starting radius (which is controlled by [member neighbor_dist]). + Does not affect normal pathfinding. To change an actor's pathfinding radius bake [NavigationMesh] resources with a different [member NavigationMesh.agent_radius] property and use different navigation maps for each actor size. </member> <member name="target_desired_distance" type="float" setter="set_target_desired_distance" getter="get_target_desired_distance" default="1.0"> The distance threshold before the final target point is considered to be reached. This will allow an agent to not have to hit the point of the final target exactly, but only the area. If this value is set to low the NavigationAgent will be stuck in a repath loop cause it will constantly overshoot or undershoot the distance to the final target point on each physics frame update. diff --git a/doc/classes/NavigationAgent3D.xml b/doc/classes/NavigationAgent3D.xml index 5e1004165d..c689ddc345 100644 --- a/doc/classes/NavigationAgent3D.xml +++ b/doc/classes/NavigationAgent3D.xml @@ -142,7 +142,8 @@ The maximum distance the agent is allowed away from the ideal path to the final location. This can happen due to trying to avoid collisions. When the maximum distance is exceeded, it recalculates the ideal path. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> - The radius of the agent. + The radius of the avoidance agent. This is the "body" of the avoidance agent and not the avoidance maneuver starting radius (which is controlled by [member neighbor_dist]). + Does not affect normal pathfinding. To change an actor's pathfinding radius bake [NavigationMesh] resources with a different [member NavigationMesh.agent_radius] property and use different navigation maps for each actor size. </member> <member name="target_desired_distance" type="float" setter="set_target_desired_distance" getter="get_target_desired_distance" default="1.0"> The distance threshold before the final target point is considered to be reached. This will allow an agent to not have to hit the point of the final target exactly, but only the area. If this value is set to low the NavigationAgent will be stuck in a repath loop cause it will constantly overshoot or undershoot the distance to the final target point on each physics frame update. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index fc86b67c60..2391cb892c 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -558,22 +558,21 @@ [b]Note:[/b] [member editor/movie_writer/disable_vsync] has no effect if the operating system or graphics driver forces V-Sync with no way for applications to disable it. </member> <member name="editor/movie_writer/fps" type="int" setter="" getter="" default="60"> - The number of frames per second to record in the video when writing a movie. Simulation speed will adjust to always match the specified framerate, which means the engine will appear to run slower at higher [member editor/movie_writer/fps] values. Certain FPS values will require you to adjust [member editor/movie_writer/mix_rate_hz] to prevent audio from desynchronizing over time. + The number of frames per second to record in the video when writing a movie. Simulation speed will adjust to always match the specified framerate, which means the engine will appear to run slower at higher [member editor/movie_writer/fps] values. Certain FPS values will require you to adjust [member editor/movie_writer/mix_rate] to prevent audio from desynchronizing over time. This can be specified manually on the command line using the [code]--fixed-fps <fps>[/code] command line argument. </member> - <member name="editor/movie_writer/mix_rate_hz" type="int" setter="" getter="" default="48000"> + <member name="editor/movie_writer/mix_rate" type="int" setter="" getter="" default="48000"> The audio mix rate to use in the recorded audio when writing a movie (in Hz). This can be different from [member audio/driver/mix_rate], but this value must be divisible by [member editor/movie_writer/fps] to prevent audio from desynchronizing over time. </member> <member name="editor/movie_writer/mjpeg_quality" type="float" setter="" getter="" default="0.75"> - The JPEG quality to use when writing a video to an AVI file, between [code]0.01[/code] and [code]1.0[/code] (inclusive). Higher [code]quality[/code] values result in better-looking output at the cost of larger file sizes. Recommended [code]quality[/code] values are between [code]0.75[/code] and [code]0.90[/code]. Even at quality [code]1.00[/code], JPEG compression remains lossy. - [b]Note:[/b] JPEG does not saving an alpha channel. If the [Image] contains an alpha channel, the image will still be saved, but the resulting JPEG file won't contain the alpha channel. + The JPEG quality to use when writing a video to an AVI file, between [code]0.01[/code] and [code]1.0[/code] (inclusive). Higher [code]quality[/code] values result in better-looking output at the cost of larger file sizes. Recommended [code]quality[/code] values are between [code]0.75[/code] and [code]0.9[/code]. Even at quality [code]1.0[/code], JPEG compression remains lossy. [b]Note:[/b] This does not affect the audio quality or writing PNG image sequences. </member> <member name="editor/movie_writer/movie_file" type="String" setter="" getter="" default=""""> The output path for the movie. The file extension determines the [MovieWriter] that will be used. Godot has 2 built-in [MovieWriter]s: - - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. AVI output is currently limited to a file of 4 GB in size at most. - - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported. + - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. MJPEG does not support transparency. AVI output is currently limited to a file of 4 GB in size at most. + - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported, even if the root viewport is set to be transparent. If you need to encode to a different format or pipe a stream through third-party software, you can extend this [MovieWriter] class to create your own movie writers. When using PNG output, the frame number will be appended at the end of the file name. It starts from 0 and is padded with 8 digits to ensure correct sorting and easier processing. For example, if the output path is [code]/tmp/hello.png[/code], the first two frames will be [code]/tmp/hello00000000.png[/code] and [code]/tmp/hello00000001.png[/code]. The audio will be saved at [code]/tmp/hello.wav[/code]. </member> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 58fdd2d058..2de185903d 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -956,6 +956,9 @@ <member name="deselect_on_focus_loss_enabled" type="bool" setter="set_deselect_on_focus_loss_enabled" getter="is_deselect_on_focus_loss_enabled" default="true"> If [code]true[/code], the selected text will be deselected when focus is lost. </member> + <member name="drag_and_drop_selection_enabled" type="bool" setter="set_drag_and_drop_selection_enabled" getter="is_drag_and_drop_selection_enabled" default="true"> + If [code]true[/code], allow drag and drop of selected text. + </member> <member name="draw_control_chars" type="bool" setter="set_draw_control_chars" getter="get_draw_control_chars" default="false"> If [code]true[/code], control characters are displayed. </member> diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 7c00cf351c..272de725c8 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1024,6 +1024,7 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_scroll_past_end_of_file_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/scroll_past_end_of_file")); text_editor->set_smooth_scroll_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/smooth_scrolling")); text_editor->set_v_scroll_speed(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/v_scroll_speed")); + text_editor->set_drag_and_drop_selection_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/drag_and_drop_selection")); // Behavior: indent text_editor->set_indent_using_spaces(EditorSettings::get_singleton()->get("text_editor/behavior/indent/type")); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 3469e96a0a..31c169a0fb 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -474,6 +474,13 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) { get_ok_button()->set_disabled(false); } +void CreateDialog::select_base() { + if (search_options_types.is_empty()) { + _update_search(); + } + select_type(base_type, false); +} + String CreateDialog::get_selected_type() { TreeItem *selected = search_options->get_selected(); if (!selected) { diff --git a/editor/create_dialog.h b/editor/create_dialog.h index 3ab27ea58c..dc8618a1c0 100644 --- a/editor/create_dialog.h +++ b/editor/create_dialog.h @@ -115,6 +115,7 @@ public: void set_base_type(const String &p_base) { base_type = p_base; } String get_base_type() const { return base_type; } + void select_base(); void set_preferred_search_result_type(const String &p_preferred_type) { preferred_search_result_type = p_preferred_type; } String get_preferred_search_result_type() { return preferred_search_result_type; } diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index 97699d0349..9a1b2b5ff5 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -487,7 +487,7 @@ void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector< void DependencyRemoveDialog::ok_pressed() { for (int i = 0; i < files_to_delete.size(); ++i) { if (ResourceCache::has(files_to_delete[i])) { - Resource *res = ResourceCache::get(files_to_delete[i]); + Ref<Resource> res = ResourceCache::get_ref(files_to_delete[i]); res->set_path(""); } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index f9a4c14c48..adbba98897 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1792,9 +1792,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it //to reload properly - if (ResourceCache::has(file)) { - Resource *r = ResourceCache::get(file); + Ref<Resource> r = ResourceCache::get_ref(file); + if (r.is_valid()) { if (!r->get_import_path().is_empty()) { String dst_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(file); r->set_import_path(dst_path); @@ -2034,9 +2034,8 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it //to reload properly - if (ResourceCache::has(p_file)) { - Resource *r = ResourceCache::get(p_file); - + Ref<Resource> r = ResourceCache::get_ref(p_file); + if (r.is_valid()) { if (!r->get_import_path().is_empty()) { String dst_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_file); r->set_import_path(dst_path); diff --git a/editor/editor_folding.cpp b/editor/editor_folding.cpp index 9e1b361f64..8c508494c0 100644 --- a/editor/editor_folding.cpp +++ b/editor/editor_folding.cpp @@ -193,10 +193,7 @@ void EditorFolding::load_scene_folding(Node *p_scene, const String &p_path) { for (int i = 0; i < res_unfolds.size(); i += 2) { String path2 = res_unfolds[i]; - Ref<Resource> res; - if (ResourceCache::has(path2)) { - res = Ref<Resource>(ResourceCache::get(path2)); - } + Ref<Resource> res = ResourceCache::get_ref(path2); if (res.is_null()) { continue; } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 0f31e3e7bb..2bf0cd2f20 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -406,7 +406,7 @@ Object *EditorProperty::get_edited_object() { return object; } -StringName EditorProperty::get_edited_property() { +StringName EditorProperty::get_edited_property() const { return property; } @@ -437,16 +437,20 @@ Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid); } -bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property) { +bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value) { bool is_valid_revert = false; Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_object, p_property, &is_valid_revert); if (!is_valid_revert) { return false; } - Variant current_value = p_object->get(p_property); + Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property); return PropertyUtils::is_property_value_different(current_value, revert_value); } +StringName EditorProperty::_get_revert_property() const { + return property; +} + void EditorProperty::update_revert_and_pin_status() { if (property == StringName()) { return; //no property, so nothing to do @@ -458,7 +462,8 @@ void EditorProperty::update_revert_and_pin_status() { CRASH_COND(!node); new_pinned = node->is_property_pinned(property); } - bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property) && !is_read_only(); + Variant current = object->get(_get_revert_property()); + bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, ¤t) && !is_read_only(); if (new_can_revert != can_revert || new_pinned != pinned) { can_revert = new_can_revert; @@ -717,11 +722,15 @@ void EditorProperty::set_bottom_editor(Control *p_control) { bottom_editor = p_control; } +Variant EditorProperty::_get_cache_value(const StringName &p_prop, bool &r_valid) const { + return object->get(p_prop, &r_valid); +} + bool EditorProperty::is_cache_valid() const { if (object) { for (const KeyValue<StringName, Variant> &E : cache) { bool valid; - Variant value = object->get(E.key, &valid); + Variant value = _get_cache_value(E.key, valid); if (!valid || value != E.value) { return false; } @@ -733,7 +742,7 @@ void EditorProperty::update_cache() { cache.clear(); if (object && property != StringName()) { bool valid; - Variant value = object->get(property, &valid); + Variant value = _get_cache_value(property, valid); if (valid) { cache[property] = value; } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 555fedf939..d70d06c48b 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -50,7 +50,7 @@ public: static bool is_property_value_different(const Variant &p_a, const Variant &p_b); static Variant get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid); - static bool can_property_revert(Object *p_object, const StringName &p_property); + static bool can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value = nullptr); }; class EditorProperty : public Container { @@ -131,6 +131,9 @@ protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; const Color *_get_property_colors(); + virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const; + virtual StringName _get_revert_property() const; + public: void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false); @@ -143,7 +146,7 @@ public: bool is_read_only() const; Object *get_edited_object(); - StringName get_edited_property(); + StringName get_edited_property() const; virtual void update_property(); void update_revert_and_pin_status(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 93caf7944c..b4b82b1edf 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -881,7 +881,7 @@ void EditorNode::_resources_changed(const Vector<String> &p_resources) { int rc = p_resources.size(); for (int i = 0; i < rc; i++) { - Ref<Resource> res(ResourceCache::get(p_resources.get(i))); + Ref<Resource> res = ResourceCache::get_ref(p_resources.get(i)); if (res.is_null()) { continue; } @@ -1011,8 +1011,8 @@ void EditorNode::_resources_reimported(const Vector<String> &p_resources) { continue; } // Reload normally. - Resource *resource = ResourceCache::get(p_resources[i]); - if (resource) { + Ref<Resource> resource = ResourceCache::get_ref(p_resources[i]); + if (resource.is_valid()) { resource->reload_from_file(); } } @@ -1620,34 +1620,6 @@ bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_nod return false; } -static bool _find_edited_resources(const Ref<Resource> &p_resource, HashSet<Ref<Resource>> &edited_resources) { - if (p_resource->is_edited()) { - edited_resources.insert(p_resource); - return true; - } - - List<PropertyInfo> plist; - - p_resource->get_property_list(&plist); - - for (const PropertyInfo &E : plist) { - if (E.type == Variant::OBJECT && E.usage & PROPERTY_USAGE_STORAGE && !(E.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)) { - Ref<Resource> res = p_resource->get(E.name); - if (res.is_null()) { - continue; - } - if (res->get_path().is_resource_file()) { // Not a subresource, continue. - continue; - } - if (_find_edited_resources(res, edited_resources)) { - return true; - } - } - } - - return false; -} - int EditorNode::_save_external_resources() { // Save external resources and its subresources if any was modified. @@ -1657,29 +1629,43 @@ int EditorNode::_save_external_resources() { } flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; - HashSet<Ref<Resource>> edited_subresources; + HashSet<String> edited_resources; int saved = 0; List<Ref<Resource>> cached; ResourceCache::get_cached_resources(&cached); - for (const Ref<Resource> &res : cached) { - if (!res->get_path().is_resource_file()) { + + for (Ref<Resource> res : cached) { + if (!res->is_edited()) { continue; } - // not only check if this resource is edited, check contained subresources too - if (_find_edited_resources(res, edited_subresources)) { - ResourceSaver::save(res->get_path(), res, flg); - saved++; - } - } - // Clear later, because user may have put the same subresource in two different resources, - // which will be shared until the next reload. + String path = res->get_path(); + if (path.begins_with("res://")) { + int subres_pos = path.find("::"); + if (subres_pos == -1) { + // Actual resource. + edited_resources.insert(path); + } else { + edited_resources.insert(path.substr(0, subres_pos)); + } + } - for (const Ref<Resource> &E : edited_subresources) { - Ref<Resource> res = E; res->set_edited(false); } + for (const String &E : edited_resources) { + Ref<Resource> res = ResourceCache::get_ref(E); + if (!res.is_valid()) { + continue; // Maybe it was erased in a thread, who knows. + } + Ref<PackedScene> ps = res; + if (ps.is_valid()) { + continue; // Do not save PackedScenes, this will mess up the editor. + } + ResourceSaver::save(res->get_path(), res, flg); + saved++; + } + return saved; } @@ -1725,7 +1711,7 @@ void EditorNode::_save_scene(String p_file, int idx) { // We must update it, but also let the previous scene state go, as // old version still work for referencing changes in instantiated or inherited scenes. - sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(ResourceCache::get(p_file))); + sdata = ResourceCache::get_ref(p_file); if (sdata.is_valid()) { sdata->recreate_state(); } else { @@ -3717,7 +3703,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b if (ResourceCache::has(lpath)) { // Used from somewhere else? No problem! Update state and replace sdata. - Ref<PackedScene> ps = Ref<PackedScene>(Object::cast_to<PackedScene>(ResourceCache::get(lpath))); + Ref<PackedScene> ps = ResourceCache::get_ref(lpath); if (ps.is_valid()) { ps->replace_state(sdata->get_state()); ps->set_last_modified_time(sdata->get_last_modified_time()); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 61b434a240..0e6c9162ce 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -43,6 +43,7 @@ #include "scene/main/window.h" #include "scene/resources/font.h" #include "scene/resources/mesh.h" +#include "scene/resources/packed_scene.h" ///////////////////// Nil ///////////////////////// @@ -3017,6 +3018,23 @@ void EditorPropertyNodePath::_set_read_only(bool p_read_only) { clear->set_disabled(p_read_only); }; +String EditorPropertyNodePath::_get_meta_pointer_property() const { + ERR_FAIL_COND_V(!pointer_mode, String()); + return SceneState::get_meta_pointer_property(get_edited_property()); +} + +Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const { + if (p_prop == get_edited_property()) { + r_valid = true; + return const_cast<EditorPropertyNodePath *>(this)->get_edited_object()->get(_get_meta_pointer_property(), &r_valid); + } + return Variant(); +} + +StringName EditorPropertyNodePath::_get_revert_property() const { + return _get_meta_pointer_property(); +} + void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { NodePath path = p_path; Node *base_node = nullptr; @@ -3048,7 +3066,11 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { if (base_node) { // for AnimationTrackKeyEdit path = base_node->get_path().rel_path_to(p_path); } - emit_changed(get_edited_property(), path); + if (pointer_mode && base_node) { + emit_changed(_get_meta_pointer_property(), path); + } else { + emit_changed(get_edited_property(), path); + } update_property(); } @@ -3064,7 +3086,11 @@ void EditorPropertyNodePath::_node_assign() { } void EditorPropertyNodePath::_node_clear() { - emit_changed(get_edited_property(), NodePath()); + if (pointer_mode) { + emit_changed(_get_meta_pointer_property(), NodePath()); + } else { + emit_changed(get_edited_property(), NodePath()); + } update_property(); } @@ -3092,7 +3118,12 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const } void EditorPropertyNodePath::update_property() { - NodePath p = get_edited_object()->get(get_edited_property()); + NodePath p; + if (pointer_mode) { + p = get_edited_object()->get(_get_meta_pointer_property()); + } else { + p = get_edited_object()->get(get_edited_property()); + } assign->set_tooltip(p); if (p == NodePath()) { @@ -3131,7 +3162,8 @@ void EditorPropertyNodePath::update_property() { assign->set_icon(EditorNode::get_singleton()->get_object_icon(target_node, "Node")); } -void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root) { +void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root, bool p_pointer_mode) { + pointer_mode = p_pointer_mode; base_hint = p_base_hint; valid_types = p_valid_types; use_path_from_scene_root = p_use_path_from_scene_root; @@ -3601,7 +3633,7 @@ static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const Stri hint.greater = true; } else if (slice == "or_lesser") { hint.lesser = true; - } else if (slice == "noslider") { + } else if (slice == "no_slider") { hint.hide_slider = true; } else if (slice == "exp") { hint.exp_range = true; @@ -3927,23 +3959,31 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ return editor; } break; case Variant::OBJECT: { - EditorPropertyResource *editor = memnew(EditorPropertyResource); - editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); - - if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) { - String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector"); - for (int i = 0; i < open_in_new.get_slice_count(","); i++) { - String type = open_in_new.get_slicec(',', i).strip_edges(); - for (int j = 0; j < p_hint_text.get_slice_count(","); j++) { - String inherits = p_hint_text.get_slicec(',', j); - if (ClassDB::is_parent_class(inherits, type)) { - editor->set_use_sub_inspector(false); + if (p_hint == PROPERTY_HINT_NODE_TYPE) { + EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath); + Vector<String> types = p_hint_text.split(",", false); + Vector<StringName> sn = Variant(types); //convert via variant + editor->setup(NodePath(), sn, false, true); + return editor; + } else { + EditorPropertyResource *editor = memnew(EditorPropertyResource); + editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); + + if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) { + String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector"); + for (int i = 0; i < open_in_new.get_slice_count(","); i++) { + String type = open_in_new.get_slicec(',', i).strip_edges(); + for (int j = 0; j < p_hint_text.get_slice_count(","); j++) { + String inherits = p_hint_text.get_slicec(',', j); + if (ClassDB::is_parent_class(inherits, type)) { + editor->set_use_sub_inspector(false); + } } } } - } - return editor; + return editor; + } } break; case Variant::DICTIONARY: { diff --git a/editor/editor_properties.h b/editor/editor_properties.h index a3b98b7724..72b2b0b283 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -704,6 +704,7 @@ class EditorPropertyNodePath : public EditorProperty { SceneTreeDialog *scene_tree = nullptr; NodePath base_hint; bool use_path_from_scene_root = false; + bool pointer_mode = false; Vector<StringName> valid_types; void _node_selected(const NodePath &p_path); @@ -714,6 +715,10 @@ class EditorPropertyNodePath : public EditorProperty { void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); bool is_drop_valid(const Dictionary &p_drag_data) const; + String _get_meta_pointer_property() const; + virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const override; + virtual StringName _get_revert_property() const override; + protected: virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); @@ -721,7 +726,7 @@ protected: public: virtual void update_property() override; - void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root = true); + void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root = true, bool p_pointer_mode = false); EditorPropertyNodePath(); }; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index db9193db06..ad9c547693 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -542,6 +542,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/behavior/navigation/scroll_past_end_of_file", false); _initial_set("text_editor/behavior/navigation/smooth_scrolling", true); EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/behavior/navigation/v_scroll_speed", 80, "1,10000,1") + _initial_set("text_editor/behavior/navigation/drag_and_drop_selection", true); // Behavior: Indent EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/behavior/indent/type", 0, "Tabs,Spaces") diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 3dd0044ab9..2d6ec0c63a 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -43,6 +43,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/import_dock.h" +#include "editor/scene_create_dialog.h" #include "editor/scene_tree_dock.h" #include "editor/shader_create_dialog.h" #include "scene/gui/label.h" @@ -1469,44 +1470,12 @@ void FileSystemDock::_make_dir_confirm() { } void FileSystemDock::_make_scene_confirm() { - String scene_name = make_scene_dialog_text->get_text().strip_edges(); - - if (scene_name.length() == 0) { - EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; - } - - String directory = path; - if (!directory.ends_with("/")) { - directory = directory.get_base_dir(); - } - - String extension = scene_name.get_extension(); - List<String> extensions; - Ref<PackedScene> sd = memnew(PackedScene); - ResourceSaver::get_recognized_extensions(sd, &extensions); - - bool extension_correct = false; - for (const String &E : extensions) { - if (E == extension) { - extension_correct = true; - break; - } - } - if (!extension_correct) { - scene_name = scene_name.get_basename() + ".tscn"; - } - - scene_name = directory.plus_file(scene_name); - - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - if (da->file_exists(scene_name)) { - EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); - return; - } + const String scene_path = make_scene_dialog->get_scene_path(); int idx = EditorNode::get_singleton()->new_scene(); - EditorNode::get_singleton()->get_editor_data().set_scene_path(idx, scene_name); + EditorNode::get_singleton()->get_editor_data().set_scene_path(idx, scene_path); + EditorNode::get_singleton()->set_edited_scene(make_scene_dialog->create_scene_root()); + EditorNode::get_singleton()->save_scene_list({ scene_path }); } void FileSystemDock::_file_removed(String p_file) { @@ -2003,10 +1972,12 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCENE: { - make_scene_dialog_text->set_text("new scene"); - make_scene_dialog_text->select_all(); - make_scene_dialog->popup_centered(Size2(250, 80) * EDSCALE); - make_scene_dialog_text->grab_focus(); + String directory = path; + if (!directory.ends_with("/")) { + directory = directory.get_base_dir(); + } + make_scene_dialog->config(directory); + make_scene_dialog->popup_centered(); } break; case FILE_NEW_SCRIPT: { @@ -3216,15 +3187,8 @@ FileSystemDock::FileSystemDock() { make_dir_dialog->register_text_enter(make_dir_dialog_text); make_dir_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_make_dir_confirm)); - make_scene_dialog = memnew(ConfirmationDialog); - make_scene_dialog->set_title(TTR("Create Scene")); - VBoxContainer *make_scene_dialog_vb = memnew(VBoxContainer); - make_scene_dialog->add_child(make_scene_dialog_vb); - - make_scene_dialog_text = memnew(LineEdit); - make_scene_dialog_vb->add_margin_child(TTR("Name:"), make_scene_dialog_text); + make_scene_dialog = memnew(SceneCreateDialog); add_child(make_scene_dialog); - make_scene_dialog->register_text_enter(make_scene_dialog_text); make_scene_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_make_scene_confirm)); make_script_dialog = memnew(ScriptCreateDialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index f20c0b2f76..f73e076ac0 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -47,6 +47,7 @@ #include "scene/gui/split_container.h" #include "scene/gui/tree.h" +class SceneCreateDialog; class ShaderCreateDialog; class FileSystemDock : public VBoxContainer { @@ -148,9 +149,8 @@ private: LineEdit *duplicate_dialog_text = nullptr; ConfirmationDialog *make_dir_dialog = nullptr; LineEdit *make_dir_dialog_text = nullptr; - ConfirmationDialog *make_scene_dialog = nullptr; - LineEdit *make_scene_dialog_text = nullptr; ConfirmationDialog *overwrite_dialog = nullptr; + SceneCreateDialog *make_scene_dialog = nullptr; ScriptCreateDialog *make_script_dialog = nullptr; ShaderCreateDialog *make_shader_dialog = nullptr; CreateDialog *new_resource_dialog = nullptr; diff --git a/editor/icons/BaseButton.svg b/editor/icons/BaseButton.svg new file mode 100644 index 0000000000..9aa0ae1c07 --- /dev/null +++ b/editor/icons/BaseButton.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m5.5 9c-.831 0-1.5.669-1.5 1.5v1.5h-2v2h12v-2h-2v-1.5c0-.831-.669-1.5-1.5-1.5z" fill="#8eef97"/></svg> diff --git a/editor/icons/GeometryInstance3D.svg b/editor/icons/GeometryInstance3D.svg new file mode 100644 index 0000000000..759d5fe413 --- /dev/null +++ b/editor/icons/GeometryInstance3D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3 1a2 2 0 0 0 -2 2 2 2 0 0 0 1 1.7304688v6.5410152a2 2 0 0 0 -1 1.728516 2 2 0 0 0 2 2 2 2 0 0 0 1.7304688-1h6.5410152a2 2 0 0 0 1.728516 1 2 2 0 0 0 2-2 2 2 0 0 0 -1.03125-1.75h.03125v-6.5214844a2 2 0 0 0 1-1.7285156 2 2 0 0 0 -2-2 2 2 0 0 0 -1.730469 1h-6.5410154a2 2 0 0 0 -1.7285156-1zm1 3h1.4140625 3.5859375 2.271484a2 2 0 0 0 .728516.7304688v1.2695312 4.585938 1.414062h-1.414062-4.585938-1.2714844a2 2 0 0 0 -.7285156-.730469v-3.269531-2.5859375z" fill="#fc7f7f"/></svg> diff --git a/editor/icons/ImporterMeshInstance3D.svg b/editor/icons/ImporterMeshInstance3D.svg new file mode 100644 index 0000000000..7e7598ac2b --- /dev/null +++ b/editor/icons/ImporterMeshInstance3D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m3 1a2 2 0 0 0 -2 2 2 2 0 0 0 1 1.7304688v6.5410152a2 2 0 0 0 -1 1.728516 2 2 0 0 0 2 2 2 2 0 0 0 1.7304688-1h6.5410152a2 2 0 0 0 1.728516 1 2 2 0 0 0 2-2 2 2 0 0 0 -1.03125-1.75h.03125v-6.5214844a2 2 0 0 0 1-1.7285156 2 2 0 0 0 -2-2 2 2 0 0 0 -1.730469 1h-6.5410154a2 2 0 0 0 -1.7285156-1zm1 3h1.4140625 3.5859375 2.271484a2 2 0 0 0 .728516.7304688v1.2695312 4.585938 1.414062h-1.414062-4.585938-1.2714844a2 2 0 0 0 -.7285156-.730469v-3.269531-2.5859375z"/><path d="m7 7h2v4h-2z"/><path d="m7 5h2v1h-2z"/></g></svg> diff --git a/editor/icons/MultiplayerSpawner.svg b/editor/icons/MultiplayerSpawner.svg new file mode 100644 index 0000000000..68ffd3aab4 --- /dev/null +++ b/editor/icons/MultiplayerSpawner.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path style="fill:none;fill-opacity:.996078;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:16.5;stroke-opacity:1;paint-order:stroke markers fill" d="M4.936 7.429A4 4 0 0 1 8 6a4 4 0 0 1 3.064 1.429M1.872 4.858A8 8 0 0 1 8 2a8 8 0 0 1 6.128 2.858"/><path d="M7 9v2H5v2h2v2h2v-2h2v-2H9V9Z" fill="#5fff97"/></svg> diff --git a/editor/icons/MultiplayerSynchronizer.svg b/editor/icons/MultiplayerSynchronizer.svg new file mode 100644 index 0000000000..1547ec5a2b --- /dev/null +++ b/editor/icons/MultiplayerSynchronizer.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path style="fill:#5fff97;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" d="M5 10h3l-2 4-2-4Z"/><path style="fill:#ff5f5f;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" d="M9 14h3l-2-4-2 4Z"/><path style="fill:none;fill-opacity:.996078;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:16.5;stroke-opacity:1;paint-order:stroke markers fill" d="M4.936 7.429A4 4 0 0 1 8 6a4 4 0 0 1 3.064 1.429M1.872 4.858A8 8 0 0 1 8 2a8 8 0 0 1 6.128 2.858"/></svg> diff --git a/editor/icons/Range.svg b/editor/icons/Range.svg new file mode 100644 index 0000000000..49311546b0 --- /dev/null +++ b/editor/icons/Range.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.8027344 2.7714844c-1.0905892.2029663-1.089037 1.7659969.00195 1.9667968l2.8808595.5761719.576172 2.8808594c.231158 1.3504655 2.264924.9453327 1.960937-.390625l-.7070279-3.5371094c-.039663-.1968491-.137665-.3771998-.28125-.5175781-.138135-.1351849-.312483-.2274468-.501953-.265625l-3.5371095-.7070312c-.1291868-.0278728-.262617-.0298643-.3925781-.0058594zm-3.9941406 4.2167968c-.6571498-.0349349-1.1683412.5633914-1.03125 1.2070313l.7070312 3.5371095c.079467.394998.3882047.703736.7832031.783203l3.5371094.707031c1.3359577.303987 1.7410905-1.729779.390625-1.960937l-2.8808594-.576172-.5761719-2.8808595c-.0369237-.1982539-.1329195-.3807141-.2753906-.5234375-.1744016-.1751556-.407488-.2795227-.6542968-.2929688z" fill="#8eef97"/></svg> diff --git a/editor/icons/VisualInstance3D.svg b/editor/icons/VisualInstance3D.svg new file mode 100644 index 0000000000..e5e43b59dd --- /dev/null +++ b/editor/icons/VisualInstance3D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><circle cx="3" cy="3" r="2"/><circle cx="13" cy="3" r="2"/><circle cx="13" cy="13" r="2"/><circle cx="3" cy="13" r="2"/></g></svg> diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index f2975b1d7a..171ef5bf4c 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -1115,7 +1115,7 @@ Ref<Animation> ResourceImporterScene::_save_animation_to_file(Ref<Animation> ani } if (ResourceCache::has(p_save_to_path)) { - Ref<Animation> old_anim = Ref<Resource>(ResourceCache::get(p_save_to_path)); + Ref<Animation> old_anim = ResourceCache::get_ref(p_save_to_path); if (old_anim.is_valid()) { old_anim->copy_from(anim); anim = old_anim; @@ -1711,7 +1711,7 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m } if (!save_to_file.is_empty()) { - Ref<Mesh> existing = Ref<Resource>(ResourceCache::get(save_to_file)); + Ref<Mesh> existing = ResourceCache::get_ref(save_to_file); if (existing.is_valid()) { //if somehow an existing one is useful, create existing->reset_state(); diff --git a/editor/import/resource_importer_texture_atlas.cpp b/editor/import/resource_importer_texture_atlas.cpp index aa338a6c0d..e5fe99890e 100644 --- a/editor/import/resource_importer_texture_atlas.cpp +++ b/editor/import/resource_importer_texture_atlas.cpp @@ -306,10 +306,8 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file //update cache if existing, else create Ref<Texture2D> cache; - if (ResourceCache::has(p_group_file)) { - Resource *resptr = ResourceCache::get(p_group_file); - cache.reference_ptr(resptr); - } else { + cache = ResourceCache::get_ref(p_group_file); + if (!cache.is_valid()) { Ref<ImageTexture> res_cache; res_cache.instantiate(); res_cache->create_from_image(new_atlas); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 1ea0299d4e..dea4aaded7 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5455,7 +5455,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & } child->set_name(name); - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(ResourceCache::get(path))); + Ref<Texture2D> texture = ResourceCache::get_ref(path); if (parent) { editor_data->get_undo_redo().add_do_method(parent, "add_child", child, true); diff --git a/editor/quick_open.cpp b/editor/quick_open.cpp index 53da945868..4938699fc4 100644 --- a/editor/quick_open.cpp +++ b/editor/quick_open.cpp @@ -146,8 +146,8 @@ void EditorQuickOpen::_confirmed() { return; } _cleanup(); - emit_signal(SNAME("quick_open")); hide(); + emit_signal(SNAME("quick_open")); } void EditorQuickOpen::cancel_pressed() { diff --git a/editor/scene_create_dialog.cpp b/editor/scene_create_dialog.cpp new file mode 100644 index 0000000000..64aea54c5f --- /dev/null +++ b/editor/scene_create_dialog.cpp @@ -0,0 +1,312 @@ +/*************************************************************************/ +/* scene_create_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_create_dialog.h" + +#include "core/io/dir_access.h" +#include "editor/create_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "scene/2d/node_2d.h" +#include "scene/3d/node_3d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/packed_scene.h" + +void SceneCreateDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + select_node_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); + node_type_2d->set_icon(get_theme_icon(SNAME("Node2D"), SNAME("EditorIcons"))); + node_type_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons"))); + node_type_gui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons"))); + node_type_other->add_theme_icon_override(SNAME("icon"), get_theme_icon(SNAME("Node"), SNAME("EditorIcons"))); + status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } break; + } +} + +void SceneCreateDialog::config(const String &p_dir) { + directory = p_dir; + root_name_edit->set_text(""); + scene_name_edit->set_text(""); + scene_name_edit->call_deferred(SNAME("grab_focus")); + update_dialog(); +} + +void SceneCreateDialog::accept_create() { + if (!get_ok_button()->is_disabled()) { + hide(); + emit_signal(SNAME("confirmed")); + } +} + +void SceneCreateDialog::browse_types() { + select_node_dialog->popup_create(true); + select_node_dialog->set_title(TTR("Pick Root Node Type")); + select_node_dialog->get_ok_button()->set_text(TTR("Pick")); +} + +void SceneCreateDialog::on_type_picked() { + other_type_display->set_text(select_node_dialog->get_selected_type().get_slice(" ", 0)); + if (node_type_other->is_pressed()) { + update_dialog(); + } else { + node_type_other->set_pressed(true); // Calls update_dialog() via group. + } +} + +void SceneCreateDialog::update_dialog() { + scene_name = scene_name_edit->get_text().strip_edges(); + update_error(file_error_label, MSG_OK, TTR("Scene name is valid.")); + + bool is_valid = true; + if (scene_name.is_empty()) { + update_error(file_error_label, MSG_ERROR, TTR("Scene name is empty.")); + is_valid = false; + } + + if (is_valid) { + if (!scene_name.ends_with(".")) { + scene_name += "."; + } + scene_name += scene_extension_picker->get_selected_metadata().operator String(); + } + + if (is_valid && !scene_name.is_valid_filename()) { + update_error(file_error_label, MSG_ERROR, TTR("File name invalid.")); + is_valid = false; + } + + if (is_valid) { + scene_name = directory.plus_file(scene_name); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists(scene_name)) { + update_error(file_error_label, MSG_ERROR, TTR("File already exists.")); + is_valid = false; + } + } + + const StringName root_type_name = StringName(other_type_display->get_text()); + if (has_theme_icon(root_type_name, SNAME("EditorIcons"))) { + node_type_other->set_icon(get_theme_icon(root_type_name, SNAME("EditorIcons"))); + } else { + node_type_other->set_icon(nullptr); + } + + update_error(node_error_label, MSG_OK, "Root node valid."); + + root_name = root_name_edit->get_text().strip_edges(); + if (root_name.is_empty()) { + root_name = scene_name.get_file().get_basename(); + } + + if (!root_name.is_valid_identifier()) { + update_error(node_error_label, MSG_ERROR, TTR("Invalid root node name.")); + is_valid = false; + } + + get_ok_button()->set_disabled(!is_valid); +} + +void SceneCreateDialog::update_error(Label *p_label, MsgType p_type, const String &p_msg) { + p_label->set_text(String::utf8("• ") + p_msg); + switch (p_type) { + case MSG_OK: + p_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); + break; + case MSG_ERROR: + p_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + break; + } +} + +String SceneCreateDialog::get_scene_path() const { + return scene_name; +} + +Node *SceneCreateDialog::create_scene_root() { + ERR_FAIL_NULL_V(node_type_group->get_pressed_button(), nullptr); + RootType type = (RootType)node_type_group->get_pressed_button()->get_meta(type_meta).operator int(); + + Node *root = nullptr; + switch (type) { + case ROOT_2D_SCENE: + root = memnew(Node2D); + break; + case ROOT_3D_SCENE: + root = memnew(Node3D); + break; + case ROOT_USER_INTERFACE: { + Control *gui = memnew(Control); + gui->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + root = gui; + } break; + case ROOT_OTHER: + root = Object::cast_to<Node>(select_node_dialog->instance_selected()); + break; + } + + ERR_FAIL_NULL_V(root, nullptr); + root->set_name(root_name); + return root; +} + +SceneCreateDialog::SceneCreateDialog() { + select_node_dialog = memnew(CreateDialog); + add_child(select_node_dialog); + select_node_dialog->set_base_type("Node"); + select_node_dialog->select_base(); + select_node_dialog->connect("create", callable_mp(this, &SceneCreateDialog::on_type_picked)); + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + + GridContainer *gc = memnew(GridContainer); + main_vb->add_child(gc); + gc->set_columns(2); + + { + Label *label = memnew(Label(TTR("Root Type:"))); + gc->add_child(label); + label->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + + VBoxContainer *vb = memnew(VBoxContainer); + gc->add_child(vb); + + node_type_group.instantiate(); + + node_type_2d = memnew(CheckBox); + vb->add_child(node_type_2d); + node_type_2d->set_text(TTR("2D Scene")); + node_type_2d->set_button_group(node_type_group); + node_type_2d->set_meta(type_meta, ROOT_2D_SCENE); + node_type_2d->set_pressed(true); + + node_type_3d = memnew(CheckBox); + vb->add_child(node_type_3d); + node_type_3d->set_text(TTR("3D Scene")); + node_type_3d->set_button_group(node_type_group); + node_type_3d->set_meta(type_meta, ROOT_3D_SCENE); + + node_type_gui = memnew(CheckBox); + vb->add_child(node_type_gui); + node_type_gui->set_text(TTR("User Interface")); + node_type_gui->set_button_group(node_type_group); + node_type_gui->set_meta(type_meta, ROOT_USER_INTERFACE); + + HBoxContainer *hb = memnew(HBoxContainer); + vb->add_child(hb); + + node_type_other = memnew(CheckBox); + hb->add_child(node_type_other); + node_type_other->set_button_group(node_type_group); + node_type_other->set_meta(type_meta, ROOT_OTHER); + + Control *spacing = memnew(Control); + hb->add_child(spacing); + spacing->set_custom_minimum_size(Size2(4 * EDSCALE, 0)); + + other_type_display = memnew(LineEdit); + hb->add_child(other_type_display); + other_type_display->set_h_size_flags(Control::SIZE_EXPAND_FILL); + other_type_display->set_editable(false); + other_type_display->set_text("Node"); + + select_node_button = memnew(Button); + hb->add_child(select_node_button); + select_node_button->connect("pressed", callable_mp(this, &SceneCreateDialog::browse_types)); + + node_type_group->connect("pressed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); + } + + { + Label *label = memnew(Label(TTR("Scene Name:"))); + gc->add_child(label); + + HBoxContainer *hb = memnew(HBoxContainer); + gc->add_child(hb); + + scene_name_edit = memnew(LineEdit); + hb->add_child(scene_name_edit); + scene_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scene_name_edit->connect("text_changed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); + scene_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1)); + + List<String> extensions; + Ref<PackedScene> sd = memnew(PackedScene); + ResourceSaver::get_recognized_extensions(sd, &extensions); + + scene_extension_picker = memnew(OptionButton); + hb->add_child(scene_extension_picker); + for (const String &E : extensions) { + scene_extension_picker->add_item("." + E); + scene_extension_picker->set_item_metadata(-1, E); + } + } + + { + Label *label = memnew(Label(TTR("Root Name:"))); + gc->add_child(label); + + root_name_edit = memnew(LineEdit); + gc->add_child(root_name_edit); + root_name_edit->set_placeholder(TTR("Leave empty to use scene name")); + root_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + root_name_edit->connect("text_changed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); + root_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1)); + } + + Control *spacing = memnew(Control); + main_vb->add_child(spacing); + spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); + + status_panel = memnew(PanelContainer); + main_vb->add_child(status_panel); + status_panel->set_h_size_flags(Control::SIZE_FILL); + status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + VBoxContainer *status_vb = memnew(VBoxContainer); + status_panel->add_child(status_vb); + + file_error_label = memnew(Label); + status_vb->add_child(file_error_label); + + node_error_label = memnew(Label); + status_vb->add_child(node_error_label); + + set_title(TTR("Create New Scene")); + set_min_size(Size2i(400 * EDSCALE, 0)); +} diff --git a/editor/scene_create_dialog.h b/editor/scene_create_dialog.h new file mode 100644 index 0000000000..5ac9d89cd7 --- /dev/null +++ b/editor/scene_create_dialog.h @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* scene_create_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_CREATE_DIALOG_H +#define SCENE_CREATE_DIALOG_H + +#include "scene/gui/dialogs.h" + +class ButtonGroup; +class CheckBox; +class CreateDialog; +class EditorFileDialog; +class Label; +class LineEdit; +class OptionButton; +class PanelContainer; + +class SceneCreateDialog : public ConfirmationDialog { + GDCLASS(SceneCreateDialog, ConfirmationDialog); + + enum MsgType { + MSG_OK, + MSG_ERROR, + }; + + const StringName type_meta = StringName("type"); + +public: + enum RootType { + ROOT_2D_SCENE, + ROOT_3D_SCENE, + ROOT_USER_INTERFACE, + ROOT_OTHER, + }; + +private: + String directory; + String scene_name; + String root_name; + + Ref<ButtonGroup> node_type_group; + CheckBox *node_type_2d = nullptr; + CheckBox *node_type_3d = nullptr; + CheckBox *node_type_gui = nullptr; + CheckBox *node_type_other = nullptr; + + LineEdit *other_type_display = nullptr; + Button *select_node_button = nullptr; + CreateDialog *select_node_dialog = nullptr; + + LineEdit *scene_name_edit = nullptr; + OptionButton *scene_extension_picker = nullptr; + LineEdit *root_name_edit = nullptr; + + PanelContainer *status_panel = nullptr; + Label *file_error_label = nullptr; + Label *node_error_label = nullptr; + + void accept_create(); + void browse_types(); + void on_type_picked(); + void update_dialog(); + void update_error(Label *p_label, MsgType p_type, const String &p_msg); + +protected: + void _notification(int p_what); + +public: + void config(const String &p_dir); + + String get_scene_path() const; + Node *create_scene_root(); + + SceneCreateDialog(); +}; + +#endif // SCENE_CREATE_DIALOG_H diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 5345143271..474c8094f2 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -746,7 +746,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a ScriptLanguage::CodeCompletionOption slider2("or_lesser", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); - ScriptLanguage::CodeCompletionOption slider3("noslider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + ScriptLanguage::CodeCompletionOption slider3("no_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider3.insert_text = slider3.display.quote(p_quote_style); r_result.insert(slider3.display, slider3); } diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp index bbbb995635..19bbd834cc 100644 --- a/modules/visual_script/visual_script_flow_control.cpp +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -803,7 +803,7 @@ public: //if the script is not in use by anyone, we can safely assume whatever we got is not casting to it. return 1; } - Ref<Script> cast_script = Ref<Resource>(ResourceCache::get(script)); + Ref<Script> cast_script = ResourceCache::get_ref(script); if (!cast_script.is_valid()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; r_error_str = "Script path is not a script: " + script; diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index 483fc8b6c3..b16358ae38 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -377,7 +377,7 @@ void VisualScriptFunctionCall::_update_method_cache() { } if (ResourceCache::has(base_script)) { - script = Ref<Resource>(ResourceCache::get(base_script)); + script = ResourceCache::get_ref(base_script); } else { return; } @@ -587,7 +587,7 @@ void VisualScriptFunctionCall::_validate_property(PropertyInfo &property) const } if (ResourceCache::has(base_script)) { - Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script)); + Ref<Script> script = ResourceCache::get_ref(base_script); if (script.is_valid()) { property.hint = PROPERTY_HINT_METHOD_OF_SCRIPT; property.hint_string = itos(script->get_instance_id()); @@ -1178,7 +1178,7 @@ void VisualScriptPropertySet::_update_cache() { } if (ResourceCache::has(base_script)) { - script = Ref<Resource>(ResourceCache::get(base_script)); + script = ResourceCache::get_ref(base_script); } else { return; } @@ -1338,7 +1338,7 @@ void VisualScriptPropertySet::_validate_property(PropertyInfo &property) const { } if (ResourceCache::has(base_script)) { - Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script)); + Ref<Script> script = ResourceCache::get_ref(base_script); if (script.is_valid()) { property.hint = PROPERTY_HINT_PROPERTY_OF_SCRIPT; property.hint_string = itos(script->get_instance_id()); @@ -1864,7 +1864,7 @@ void VisualScriptPropertyGet::_update_cache() { } if (ResourceCache::has(base_script)) { - script = Ref<Resource>(ResourceCache::get(base_script)); + script = ResourceCache::get_ref(base_script); } else { return; } @@ -2044,7 +2044,7 @@ void VisualScriptPropertyGet::_validate_property(PropertyInfo &property) const { } if (ResourceCache::has(base_script)) { - Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script)); + Ref<Script> script = ResourceCache::get_ref(base_script); if (script.is_valid()) { property.hint = PROPERTY_HINT_PROPERTY_OF_SCRIPT; property.hint_string = itos(script->get_instance_id()); diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index 10f23b320b..81802298d9 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -381,13 +381,3 @@ MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int a return godot_button_mask; } - -void AndroidInputHandler::process_scroll(Point2 p_pos) { - Ref<InputEventPanGesture> ev; - ev.instantiate(); - _set_key_modifier_state(ev); - ev->set_position(p_pos); - ev->set_delta(p_pos - scroll_prev_pos); - Input::get_singleton()->parse_input_event(ev); - scroll_prev_pos = p_pos; -} diff --git a/platform/android/android_input_handler.h b/platform/android/android_input_handler.h index e9c0ec1475..1397ca59e4 100644 --- a/platform/android/android_input_handler.h +++ b/platform/android/android_input_handler.h @@ -69,7 +69,6 @@ private: Vector<TouchPos> touch; Point2 hover_prev_pos; // needed to calculate the relative position on hover events - Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events void _set_key_modifier_state(Ref<InputEventWithModifiers> ev); @@ -83,7 +82,6 @@ public: void process_hover(int p_type, Point2 p_pos); void process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); void process_double_tap(int event_android_button_mask, Point2 p_pos); - void process_scroll(Point2 p_pos); void process_joy_event(JoypadEvent p_event); void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); }; diff --git a/platform/android/detect.py b/platform/android/detect.py index 0099ac7e0d..47cfade765 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -1,7 +1,7 @@ import os import sys import platform -from distutils.version import LooseVersion +import subprocess def is_active(): @@ -13,41 +13,35 @@ def get_name(): def can_build(): - return ("ANDROID_SDK_ROOT" in os.environ) or ("ANDROID_HOME" in os.environ) - - -def get_platform(platform): - return int(platform.split("-")[1]) + return os.path.exists(get_env_android_sdk_root()) def get_opts(): from SCons.Variables import BoolVariable, EnumVariable return [ - ("ANDROID_NDK_ROOT", "Path to the Android NDK", get_android_ndk_root()), - ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_android_sdk_root()), + ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_env_android_sdk_root()), ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"), EnumVariable("android_arch", "Target architecture", "arm64v8", ("armv7", "arm64v8", "x86", "x86_64")), ] # Return the ANDROID_SDK_ROOT environment variable. -# While ANDROID_HOME has been deprecated, it's used as a fallback for backward -# compatibility purposes. -def get_android_sdk_root(): - if "ANDROID_SDK_ROOT" in os.environ: - return os.environ.get("ANDROID_SDK_ROOT", 0) - else: - return os.environ.get("ANDROID_HOME", 0) +def get_env_android_sdk_root(): + return os.environ.get("ANDROID_SDK_ROOT", -1) -# Return the ANDROID_NDK_ROOT environment variable. -# We generate one for this build using the ANDROID_SDK_ROOT env -# variable and the project ndk version. -# If the env variable is already defined, we override it with -# our own to match what the project expects. -def get_android_ndk_root(): - return get_android_sdk_root() + "/ndk/" + get_project_ndk_version() +def get_min_sdk_version(platform): + return int(platform.split("-")[1]) + + +def get_android_ndk_root(env): + return env["ANDROID_SDK_ROOT"] + "/ndk/" + get_ndk_version() + + +# This is kept in sync with the value in 'platform/android/java/app/config.gradle'. +def get_ndk_version(): + return "23.2.8568313" def get_flags(): @@ -56,133 +50,70 @@ def get_flags(): ] -def create(env): - tools = env["TOOLS"] - if "mingw" in tools: - tools.remove("mingw") - if "applelink" in tools: - tools.remove("applelink") - env.Tool("gcc") - return env.Clone(tools=tools) - - -# Check if ANDROID_NDK_ROOT is valid. -# If not, install the ndk using ANDROID_SDK_ROOT and sdkmanager. +# Check if Android NDK version is installed +# If not, install it. def install_ndk_if_needed(env): print("Checking for Android NDK...") - env_ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"]) - if env_ndk_version is None: - # Reinstall the ndk and update ANDROID_NDK_ROOT. - print("Installing Android NDK...") - if env["ANDROID_SDK_ROOT"] is None: - raise Exception("Invalid ANDROID_SDK_ROOT environment variable.") - - import subprocess - + sdk_root = env["ANDROID_SDK_ROOT"] + if not os.path.exists(get_android_ndk_root(env)): extension = ".bat" if os.name == "nt" else "" - sdkmanager_path = env["ANDROID_SDK_ROOT"] + "/cmdline-tools/latest/bin/sdkmanager" + extension - ndk_download_args = "ndk;" + get_project_ndk_version() - subprocess.check_call([sdkmanager_path, ndk_download_args]) - - env["ANDROID_NDK_ROOT"] = env["ANDROID_SDK_ROOT"] + "/ndk/" + get_project_ndk_version() - print("ANDROID_NDK_ROOT: " + env["ANDROID_NDK_ROOT"]) + sdkmanager = sdk_root + "/cmdline-tools/latest/bin/sdkmanager" + extension + if os.path.exists(sdkmanager): + # Install the Android NDK + print("Installing Android NDK...") + ndk_download_args = "ndk;" + get_ndk_version() + subprocess.check_call([sdkmanager, ndk_download_args]) + else: + print("Cannot find " + sdkmanager) + print( + "Please ensure ANDROID_SDK_ROOT is correct and cmdline-tools are installed, or install NDK version " + + get_ndk_version() + + " manually." + ) + sys.exit() + env["ANDROID_NDK_ROOT"] = get_android_ndk_root(env) def configure(env): install_ndk_if_needed(env) - - # Workaround for MinGW. See: - # https://www.scons.org/wiki/LongCmdLinesOnWin32 - if os.name == "nt": - - import subprocess - - def mySubProcess(cmdline, env): - # print("SPAWNED : " + cmdline) - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - proc = subprocess.Popen( - cmdline, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - startupinfo=startupinfo, - shell=False, - env=env, - ) - data, err = proc.communicate() - rv = proc.wait() - if rv: - print("=====") - print(err) - print("=====") - return rv - - def mySpawn(sh, escape, cmd, args, env): - - newargs = " ".join(args[1:]) - cmdline = cmd + " " + newargs - - rv = 0 - if len(cmdline) > 32000 and cmd.endswith("ar"): - cmdline = cmd + " " + args[1] + " " + args[2] + " " - for i in range(3, len(args)): - rv = mySubProcess(cmdline + args[i], env) - if rv: - break - else: - rv = mySubProcess(cmdline, env) - - return rv - - env["SPAWN"] = mySpawn + ndk_root = env["ANDROID_NDK_ROOT"] # Architecture if env["android_arch"] not in ["armv7", "arm64v8", "x86", "x86_64"]: - env["android_arch"] = "armv7" + env["android_arch"] = "arm64v8" print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")") - can_vectorize = True - if env["android_arch"] == "x86": - env["ARCH"] = "arch-x86" - env.extra_suffix = ".x86" + env.extra_suffix - target_subpath = "x86-4.9" - abi_subpath = "i686-linux-android" - arch_subpath = "x86" - env["x86_libtheora_opt_gcc"] = True - elif env["android_arch"] == "x86_64": - if get_platform(env["ndk_platform"]) < 21: + if get_min_sdk_version(env["ndk_platform"]) < 21: + if env["android_arch"] == "x86_64" or env["android_arch"] == "arm64v8": print( - "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting" - " ndk_platform=android-21" + "WARNING: android_arch=" + + env["android_arch"] + + " is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21" ) env["ndk_platform"] = "android-21" - env["ARCH"] = "arch-x86_64" - env.extra_suffix = ".x86_64" + env.extra_suffix - target_subpath = "x86_64-4.9" - abi_subpath = "x86_64-linux-android" - arch_subpath = "x86_64" - env["x86_libtheora_opt_gcc"] = True - elif env["android_arch"] == "armv7": - env["ARCH"] = "arch-arm" - target_subpath = "arm-linux-androideabi-4.9" - abi_subpath = "arm-linux-androideabi" - arch_subpath = "armeabi-v7a" + + if env["android_arch"] == "armv7": + target_triple = "armv7a-linux-androideabi" + bin_utils = "arm-linux-androideabi" env.extra_suffix = ".armv7" + env.extra_suffix elif env["android_arch"] == "arm64v8": - if get_platform(env["ndk_platform"]) < 21: - print( - "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting" - " ndk_platform=android-21" - ) - env["ndk_platform"] = "android-21" - env["ARCH"] = "arch-arm64" - target_subpath = "aarch64-linux-android-4.9" - abi_subpath = "aarch64-linux-android" - arch_subpath = "arm64-v8a" + target_triple = "aarch64-linux-android" + bin_utils = target_triple env.extra_suffix = ".armv8" + env.extra_suffix + elif env["android_arch"] == "x86": + target_triple = "i686-linux-android" + bin_utils = target_triple + env.extra_suffix = ".x86" + env.extra_suffix + elif env["android_arch"] == "x86_64": + target_triple = "x86_64-linux-android" + bin_utils = target_triple + env.extra_suffix = ".x86_64" + env.extra_suffix + + target_option = ["-target", target_triple + str(get_min_sdk_version(env["ndk_platform"]))] + env.Append(CCFLAGS=target_option) + env.Append(LINKFLAGS=target_option) # Build type @@ -191,15 +122,11 @@ def configure(env): # `-O2` is more friendly to debuggers than `-O3`, leading to better crash backtraces # when using `target=release_debug`. opt = "-O3" if env["target"] == "release" else "-O2" - env.Append(LINKFLAGS=[opt]) env.Append(CCFLAGS=[opt, "-fomit-frame-pointer"]) elif env["optimize"] == "size": # optimize for size - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - + env.Append(CCFLAGS=["-Oz"]) env.Append(CPPDEFINES=["NDEBUG"]) - if can_vectorize: - env.Append(CCFLAGS=["-ftree-vectorize"]) + env.Append(CCFLAGS=["-ftree-vectorize"]) elif env["target"] == "debug": env.Append(LINKFLAGS=["-O0"]) env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"]) @@ -211,7 +138,6 @@ def configure(env): env["SHLIBSUFFIX"] = ".so" if env["PLATFORM"] == "win32": - env.Tool("gcc") env.use_windows_spawn_fix() if sys.platform.startswith("linux"): @@ -224,32 +150,15 @@ def configure(env): else: host_subpath = "windows" - compiler_path = env["ANDROID_NDK_ROOT"] + "/toolchains/llvm/prebuilt/" + host_subpath + "/bin" - gcc_toolchain_path = env["ANDROID_NDK_ROOT"] + "/toolchains/" + target_subpath + "/prebuilt/" + host_subpath - tools_path = gcc_toolchain_path + "/" + abi_subpath + "/bin" - - # For Clang to find NDK tools in preference of those system-wide - env.PrependENVPath("PATH", tools_path) - - ccache_path = os.environ.get("CCACHE") - if ccache_path is None: - env["CC"] = compiler_path + "/clang" - env["CXX"] = compiler_path + "/clang++" - else: - # there aren't any ccache wrappers available for Android, - # to enable caching we need to prepend the path to the ccache binary - env["CC"] = ccache_path + " " + compiler_path + "/clang" - env["CXX"] = ccache_path + " " + compiler_path + "/clang++" - env["AR"] = tools_path + "/ar" - env["RANLIB"] = tools_path + "/ranlib" - env["AS"] = tools_path + "/as" + toolchain_path = ndk_root + "/toolchains/llvm/prebuilt/" + host_subpath + compiler_path = toolchain_path + "/bin" + bin_utils_path = toolchain_path + "/" + bin_utils + "/bin" - common_opts = ["-gcc-toolchain", gcc_toolchain_path] - - # Compile flags - - env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/include"]) - env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"]) + env["CC"] = compiler_path + "/clang" + env["CXX"] = compiler_path + "/clang++" + env["AR"] = compiler_path + "/llvm-ar" + env["RANLIB"] = compiler_path + "/llvm-ranlib" + env["AS"] = bin_utils_path + "/as" # Disable exceptions and rtti on non-tools (template) builds if env["tools"]: @@ -261,100 +170,31 @@ def configure(env): # Don't use dynamic_cast, necessary with no-rtti. env.Append(CPPDEFINES=["NO_SAFE_CAST"]) - lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env["ndk_platform"] + "/" + env["ARCH"] - - # Using NDK unified headers (NDK r15+) - sysroot = env["ANDROID_NDK_ROOT"] + "/sysroot" - env.Append(CPPFLAGS=["--sysroot=" + sysroot]) - env.Append(CPPFLAGS=["-isystem", sysroot + "/usr/include/" + abi_subpath]) - env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/android/support/include"]) - # For unified headers this define has to be set manually - env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))]) - env.Append( CCFLAGS=( - "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden" - " -fno-strict-aliasing".split() + "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split() ) ) env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"]) - if get_platform(env["ndk_platform"]) >= 24: + if get_min_sdk_version(env["ndk_platform"]) >= 24: env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)]) if env["android_arch"] == "x86": - target_opts = ["-target", "i686-none-linux-android"] - # The NDK adds this if targeting API < 21, so we can drop it when Godot targets it at least + # The NDK adds this if targeting API < 24, so we can drop it when Godot targets it at least env.Append(CCFLAGS=["-mstackrealign"]) - - elif env["android_arch"] == "x86_64": - target_opts = ["-target", "x86_64-none-linux-android"] - elif env["android_arch"] == "armv7": - target_opts = ["-target", "armv7-none-linux-androideabi"] env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split()) env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"]) - # Enable ARM NEON instructions to compile more optimized code. - env.Append(CCFLAGS=["-mfpu=neon"]) env.Append(CPPDEFINES=["__ARM_NEON__"]) - elif env["android_arch"] == "arm64v8": - target_opts = ["-target", "aarch64-none-linux-android"] env.Append(CCFLAGS=["-mfix-cortex-a53-835769"]) env.Append(CPPDEFINES=["__ARM_ARCH_8A__"]) - env.Append(CCFLAGS=target_opts) - env.Append(CCFLAGS=common_opts) - # Link flags - ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"]) - if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"): - env.Append(LINKFLAGS=["-Wl,--exclude-libs,libgcc.a", "-Wl,--exclude-libs,libatomic.a", "-nostdlib++"]) - else: - env.Append( - LINKFLAGS=[ - env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libandroid_support.a" - ] - ) - env.Append(LINKFLAGS=["-shared", "--sysroot=" + lib_sysroot, "-Wl,--warn-shared-textrel"]) - env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/"]) - env.Append( - LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libc++_shared.so"] - ) - - if env["android_arch"] == "armv7": - env.Append(LINKFLAGS="-Wl,--fix-cortex-a8".split()) - env.Append(LINKFLAGS="-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now".split()) - env.Append(LINKFLAGS="-Wl,-soname,libgodot_android.so -Wl,--gc-sections".split()) - - env.Append(LINKFLAGS=target_opts) - env.Append(LINKFLAGS=common_opts) - - env.Append( - LIBPATH=[ - env["ANDROID_NDK_ROOT"] - + "/toolchains/" - + target_subpath - + "/prebuilt/" - + host_subpath - + "/lib/gcc/" - + abi_subpath - + "/4.9.x" - ] - ) - env.Append( - LIBPATH=[ - env["ANDROID_NDK_ROOT"] - + "/toolchains/" - + target_subpath - + "/prebuilt/" - + host_subpath - + "/" - + abi_subpath - + "/lib" - ] - ) + env.Append(LINKFLAGS="-Wl,--gc-sections -Wl,--no-undefined -Wl,-z,now".split()) + env.Append(LINKFLAGS="-Wl,-soname,libgodot_android.so") env.Prepend(CPPPATH=["#platform/android"]) env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED", "NO_FCNTL"]) @@ -364,25 +204,3 @@ def configure(env): env.Append(CPPDEFINES=["VULKAN_ENABLED"]) if not env["use_volk"]: env.Append(LIBS=["vulkan"]) - - -# Return the project NDK version. -# This is kept in sync with the value in 'platform/android/java/app/config.gradle'. -def get_project_ndk_version(): - return "21.4.7075529" - - -# Return NDK version string in source.properties (adapted from the Chromium project). -def get_env_ndk_version(path): - if path is None: - return None - prop_file_path = os.path.join(path, "source.properties") - try: - with open(prop_file_path) as prop_file: - for line in prop_file: - key_value = list(map(lambda x: x.strip(), line.split("="))) - if key_value[0] == "Pkg.Revision": - return key_value[1] - except Exception: - print("Could not read source prop file '%s'" % prop_file_path) - return None diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index c343b48ca3..3daf628e63 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -8,7 +8,7 @@ ext.versions = [ fragmentVersion : '1.3.6', nexusPublishVersion: '1.1.0', javaVersion : 11, - ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. + ndkVersion : '23.2.8568313' // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. ] diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 1f8f8c82a6..3182ab0666 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -114,11 +114,6 @@ public class GodotLib { public static native void doubleTap(int buttonMask, int x, int y); /** - * Forward scroll events from the main thread to the GL thread. - */ - public static native void scroll(int x, int y); - - /** * Forward accelerometer sensor events from the main thread to the GL thread. * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java index ac13cad23e..778efa914a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java @@ -80,15 +80,6 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener } @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - //Log.i("GodotGesture", "onScroll"); - final int x = Math.round(distanceX); - final int y = Math.round(distanceY); - GodotLib.scroll(x, y); - return true; - } - - @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { //Log.i("GodotGesture", "onFling"); return true; diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index de666f1b11..eaffe14b13 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -306,15 +306,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env } // Called on the UI thread -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) { - if (step.get() <= 0) { - return; - } - - input_handler->process_scroll(Point2(p_x, p_y)); -} - -// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) { if (step.get() <= 0) { return; diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index e1d30eea77..aa8d67cf46 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -51,7 +51,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEn JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index b2b848d380..4599785ce4 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -437,7 +437,7 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_lesser,or_greater,noslider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_lesser,or_greater,no_slider,suffix:px"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew"); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 74ac96c6cc..abe942b97a 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -116,7 +116,7 @@ void LightmapGIData::_set_light_textures_data(const Array &p_data) { Array LightmapGIData::_get_light_textures_data() const { Array ret; - if (light_texture.is_null()) { + if (light_texture.is_null() || light_texture->get_layers() == 0) { return ret; } diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index ea5206b3c5..4c00250162 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -1026,7 +1026,7 @@ void Node3D::_bind_methods() { ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_lesser,no_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis"); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index a54e728c1b..e9c33b1839 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -701,7 +701,7 @@ void LiveEditor::_res_set_func(int p_id, const StringName &p_prop, const Variant return; } - Ref<Resource> r = ResourceCache::get(resp); + Ref<Resource> r = ResourceCache::get_ref(resp); if (!r.is_valid()) { return; } @@ -728,7 +728,7 @@ void LiveEditor::_res_call_func(int p_id, const StringName &p_method, const Vari return; } - Ref<Resource> r = ResourceCache::get(resp); + Ref<Resource> r = ResourceCache::get_ref(resp); if (!r.is_valid()) { return; } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 7b97b0fa47..118e77c009 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -723,8 +723,20 @@ void Control::_notification(int p_notification) { data.parent_window = Object::cast_to<Window>(get_parent()); data.is_rtl_dirty = true; + if (data.theme.is_null()) { + if (data.parent && (data.parent->data.theme_owner || data.parent->data.theme_owner_window)) { + data.theme_owner = data.parent->data.theme_owner; + data.theme_owner_window = data.parent->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else if (data.parent_window && (data.parent_window->theme_owner || data.parent_window->theme_owner_window)) { + data.theme_owner = data.parent_window->theme_owner; + data.theme_owner_window = data.parent_window->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } + CanvasItem *node = this; - Control *parent_control = nullptr; + bool has_parent_control = false; while (!node->is_set_as_top_level()) { CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent()); @@ -732,22 +744,19 @@ void Control::_notification(int p_notification) { break; } - parent_control = Object::cast_to<Control>(parent); + Control *parent_control = Object::cast_to<Control>(parent); if (parent_control) { + has_parent_control = true; break; } node = parent; } - if (parent_control) { + if (has_parent_control) { // Do nothing, has a parent control. - if (data.theme.is_null() && parent_control->data.theme_owner) { - data.theme_owner = parent_control->data.theme_owner; - notification(NOTIFICATION_THEME_CHANGED); - } } else { - //is a regular root control or top_level + // Is a regular root control or top_level. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); data.RI = viewport->_gui_add_root_control(this); @@ -758,7 +767,7 @@ void Control::_notification(int p_notification) { if (data.parent_canvas_item) { data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed)); } else { - //connect viewport + // Connect viewport. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); viewport->connect("size_changed", callable_mp(this, &Control::_size_changed)); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 192d214262..a2b05ee50d 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -262,7 +262,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin Button *AcceptDialog::add_cancel_button(const String &p_cancel) { String c = p_cancel; if (p_cancel.is_empty()) { - c = TTRC("Cancel"); + c = RTR("Cancel"); } Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); @@ -349,7 +349,7 @@ AcceptDialog::AcceptDialog() { hbc->add_spacer(); ok = memnew(Button); - ok->set_text(TTRC("OK")); + ok->set_text(RTR("OK")); hbc->add_child(ok); hbc->add_spacer(); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 1725816c31..0e0f8bde83 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -337,7 +337,7 @@ void FileDialog::_action_pressed() { } if (dir_access->file_exists(f)) { - confirm_save->set_text(TTRC("File exists, overwrite?")); + confirm_save->set_text(RTR("File exists, overwrite?")); confirm_save->popup_centered(Size2(200, 80)); } else { emit_signal(SNAME("file_selected"), f); @@ -423,10 +423,10 @@ void FileDialog::deselect_all() { switch (mode) { case FILE_MODE_OPEN_FILE: case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(TTRC("Open")); + get_ok_button()->set_text(RTR("Open")); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(TTRC("Select Current Folder")); + get_ok_button()->set_text(RTR("Select Current Folder")); break; case FILE_MODE_OPEN_ANY: case FILE_MODE_SAVE_FILE: @@ -450,7 +450,7 @@ void FileDialog::_tree_selected() { if (!d["dir"]) { file->set_text(d["name"]); } else if (mode == FILE_MODE_OPEN_DIR) { - get_ok_button()->set_text(TTRC("Select This Folder")); + get_ok_button()->set_text(RTR("Select This Folder")); } get_ok_button()->set_disabled(_is_open_should_be_disabled()); @@ -502,7 +502,7 @@ void FileDialog::update_file_list() { if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { message->hide(); } else { - message->set_text(TTRC("You don't have permission to access contents of this folder.")); + message->set_text(RTR("You don't have permission to access contents of this folder.")); message->show(); } @@ -746,35 +746,35 @@ void FileDialog::set_file_mode(FileMode p_mode) { mode = p_mode; switch (mode) { case FILE_MODE_OPEN_FILE: - get_ok_button()->set_text(TTRC("Open")); + get_ok_button()->set_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open a File")); } makedir->hide(); break; case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(TTRC("Open")); + get_ok_button()->set_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open File(s)")); } makedir->hide(); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(TTRC("Select Current Folder")); + get_ok_button()->set_text(RTR("Select Current Folder")); if (mode_overrides_title) { set_title(TTRC("Open a Directory")); } makedir->show(); break; case FILE_MODE_OPEN_ANY: - get_ok_button()->set_text(TTRC("Open")); + get_ok_button()->set_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open a File or Directory")); } makedir->show(); break; case FILE_MODE_SAVE_FILE: - get_ok_button()->set_text(TTRC("Save")); + get_ok_button()->set_text(RTR("Save")); if (mode_overrides_title) { set_title(TTRC("Save a File")); } @@ -964,13 +964,13 @@ FileDialog::FileDialog() { dir_prev = memnew(Button); dir_prev->set_flat(true); - dir_prev->set_tooltip(TTRC("Go to previous folder.")); + dir_prev->set_tooltip(RTR("Go to previous folder.")); dir_next = memnew(Button); dir_next->set_flat(true); - dir_next->set_tooltip(TTRC("Go to next folder.")); + dir_next->set_tooltip(RTR("Go to next folder.")); dir_up = memnew(Button); dir_up->set_flat(true); - dir_up->set_tooltip(TTRC("Go to parent folder.")); + dir_up->set_tooltip(RTR("Go to parent folder.")); hbc->add_child(dir_prev); hbc->add_child(dir_next); hbc->add_child(dir_up); @@ -978,7 +978,7 @@ FileDialog::FileDialog() { dir_next->connect("pressed", callable_mp(this, &FileDialog::_go_forward)); dir_up->connect("pressed", callable_mp(this, &FileDialog::_go_up)); - hbc->add_child(memnew(Label(TTRC("Path:")))); + hbc->add_child(memnew(Label(RTR("Path:")))); drives_container = memnew(HBoxContainer); hbc->add_child(drives_container); @@ -994,7 +994,7 @@ FileDialog::FileDialog() { refresh = memnew(Button); refresh->set_flat(true); - refresh->set_tooltip(TTRC("Refresh files.")); + refresh->set_tooltip(RTR("Refresh files.")); refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list)); hbc->add_child(refresh); @@ -1002,7 +1002,7 @@ FileDialog::FileDialog() { show_hidden->set_flat(true); show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); - show_hidden->set_tooltip(TTRC("Toggle the visibility of hidden files.")); + show_hidden->set_tooltip(RTR("Toggle the visibility of hidden files.")); show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); @@ -1010,14 +1010,14 @@ FileDialog::FileDialog() { hbc->add_child(shortcuts_container); makedir = memnew(Button); - makedir->set_text(TTRC("Create Folder")); + makedir->set_text(RTR("Create Folder")); makedir->connect("pressed", callable_mp(this, &FileDialog::_make_dir)); hbc->add_child(makedir); vbox->add_child(hbc); tree = memnew(Tree); tree->set_hide_root(true); - vbox->add_margin_child(TTRC("Directories & Files:"), tree, true); + vbox->add_margin_child(RTR("Directories & Files:"), tree, true); message = memnew(Label); message->hide(); @@ -1027,7 +1027,7 @@ FileDialog::FileDialog() { tree->add_child(message); file_box = memnew(HBoxContainer); - file_box->add_child(memnew(Label(TTRC("File:")))); + file_box->add_child(memnew(Label(RTR("File:")))); file = memnew(LineEdit); file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); file->set_stretch_ratio(4); @@ -1058,22 +1058,22 @@ FileDialog::FileDialog() { confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed)); makedialog = memnew(ConfirmationDialog); - makedialog->set_title(TTRC("Create Folder")); + makedialog->set_title(RTR("Create Folder")); VBoxContainer *makevb = memnew(VBoxContainer); makedialog->add_child(makevb); makedirname = memnew(LineEdit); makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); - makevb->add_margin_child(TTRC("Name:"), makedirname); + makevb->add_margin_child(RTR("Name:"), makedirname); add_child(makedialog, false, INTERNAL_MODE_FRONT); makedialog->register_text_enter(makedirname); makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm)); mkdirerr = memnew(AcceptDialog); - mkdirerr->set_text(TTRC("Could not create folder.")); + mkdirerr->set_text(RTR("Could not create folder.")); add_child(mkdirerr, false, INTERNAL_MODE_FRONT); exterr = memnew(AcceptDialog); - exterr->set_text(TTRC("Must use a valid extension.")); + exterr->set_text(RTR("Must use a valid extension.")); add_child(exterr, false, INTERNAL_MODE_FRONT); update_filters(); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index f36682942f..80859e8eb9 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -126,7 +126,11 @@ int ProgressBar::get_fill_mode() { } void ProgressBar::set_percent_visible(bool p_visible) { + if (percent_visible == p_visible) { + return; + } percent_visible = p_visible; + update_minimum_size(); update(); } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 8299d73b68..fa929344d4 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -727,6 +727,7 @@ void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { if (!get_clip_tabs()) { update_minimum_size(); } + call_deferred(SNAME("_repaint")); } bool TabContainer::is_tab_hidden(int p_tab) const { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index d9a91590f7..5506616b7a 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1723,7 +1723,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { update(); } - } else if (is_mouse_over_selection()) { + } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) { selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; selection.drag_attempt = true; } else { @@ -4163,6 +4163,14 @@ bool TextEdit::is_deselect_on_focus_loss_enabled() const { return deselect_on_focus_loss_enabled; } +void TextEdit::set_drag_and_drop_selection_enabled(const bool p_enabled) { + drag_and_drop_selection_enabled = p_enabled; +} + +bool TextEdit::is_drag_and_drop_selection_enabled() const { + return drag_and_drop_selection_enabled; +} + void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } @@ -5243,6 +5251,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &TextEdit::set_deselect_on_focus_loss_enabled); ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &TextEdit::is_deselect_on_focus_loss_enabled); + ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled); + ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled); + ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color); ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color); @@ -5405,6 +5416,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 993203bee6..9de2982d0a 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -422,6 +422,7 @@ private: bool selecting_enabled = true; bool deselect_on_focus_loss_enabled = true; + bool drag_and_drop_selection_enabled = true; Color font_selected_color = Color(1, 1, 1); Color selection_color = Color(1, 1, 1); @@ -795,6 +796,9 @@ public: void set_deselect_on_focus_loss_enabled(const bool p_enabled); bool is_deselect_on_focus_loss_enabled() const; + void set_drag_and_drop_selection_enabled(const bool p_enabled); + bool is_drag_and_drop_selection_enabled() const; + void set_override_selected_font_color(bool p_override_selected_font_color); bool is_overriding_selected_font_color() const; diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 122e36904b..86334882fa 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -174,6 +174,28 @@ void VideoStreamPlayer::_notification(int p_notification) { Size2 s = expand ? get_size() : texture->get_size(); draw_texture_rect(texture, Rect2(Point2(), s), false); } break; + + case NOTIFICATION_PAUSED: { + if (is_playing() && !is_paused()) { + paused_from_tree = true; + if (playback.is_valid()) { + playback->set_paused(true); + set_process_internal(false); + } + last_audio_time = 0; + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (paused_from_tree) { + paused_from_tree = false; + if (playback.is_valid()) { + playback->set_paused(false); + set_process_internal(true); + } + last_audio_time = 0; + } + } break; } } @@ -255,6 +277,10 @@ void VideoStreamPlayer::play() { playback->play(); set_process_internal(true); last_audio_time = 0; + + if (!can_process()) { + _notification(NOTIFICATION_PAUSED); + } } void VideoStreamPlayer::stop() { @@ -281,6 +307,14 @@ bool VideoStreamPlayer::is_playing() const { void VideoStreamPlayer::set_paused(bool p_paused) { paused = p_paused; + if (!p_paused && !can_process()) { + paused_from_tree = true; + return; + } else if (p_paused && paused_from_tree) { + paused_from_tree = false; + return; + } + if (playback.is_valid()) { playback->set_paused(p_paused); set_process_internal(!p_paused); diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 130b2901f1..d2822a989b 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -60,6 +60,7 @@ class VideoStreamPlayer : public Control { int wait_resampler_limit = 2; bool paused = false; + bool paused_from_tree = false; bool autoplay = false; float volume = 1.0; double last_audio_time = 0.0; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index a30eb036db..545ff68b72 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -384,11 +384,7 @@ void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { for (int i = motion_from; i <= motion_to; i++) { data.children[i]->notification(NOTIFICATION_MOVED_IN_PARENT); } - for (const KeyValue<StringName, GroupData> &E : p_child->data.grouped) { - if (E.value.group) { - E.value.group->changed = true; - } - } + p_child->_propagate_groups_dirty(); data.blocked--; } @@ -408,6 +404,18 @@ void Node::raise() { } } +void Node::_propagate_groups_dirty() { + for (const KeyValue<StringName, GroupData> &E : data.grouped) { + if (E.value.group) { + E.value.group->changed = true; + } + } + + for (int i = 0; i < data.children.size(); i++) { + data.children[i]->_propagate_groups_dirty(); + } +} + void Node::add_child_notify(Node *p_child) { // to be used when not wanted } diff --git a/scene/main/node.h b/scene/main/node.h index 5b7bc0a587..3c4727f11c 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -174,6 +174,7 @@ private: void _propagate_after_exit_tree(); void _print_orphan_nodes(); void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification); + void _propagate_groups_dirty(); Array _get_node_and_resource(const NodePath &p_path); void _duplicate_signals(const Node *p_original, Node *p_copy) const; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 69fb5fdf07..73e8f537d9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -822,6 +822,22 @@ void Window::_notification(int p_what) { emit_signal(SceneStringNames::get_singleton()->visibility_changed); RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); } + + if (theme.is_null()) { + Control *parent_c = cast_to<Control>(get_parent()); + if (parent_c && (parent_c->data.theme_owner || parent_c->data.theme_owner_window)) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else { + Window *parent_w = cast_to<Window>(get_parent()); + if (parent_w && (parent_w->theme_owner || parent_w->theme_owner_window)) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } + } } break; case NOTIFICATION_READY: { diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b90f396110..2c58aa83a9 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -35,6 +35,7 @@ #include "core/core_string_names.h" #include "core/io/missing_resource.h" #include "core/io/resource_loader.h" +#include "core/templates/local_vector.h" #include "scene/2d/node_2d.h" #include "scene/3d/node_3d.h" #include "scene/gui/control.h" @@ -43,7 +44,7 @@ #include "scene/property_utils.h" #define PACKED_SCENE_VERSION 2 - +#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_" bool SceneState::can_instantiate() const { return nodes.size() > 0; } @@ -108,6 +109,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { HashMap<Ref<Resource>, Ref<Resource>> resources_local_to_scene; + LocalVector<DeferredNodePathProperties> deferred_node_paths; + for (int i = 0; i < nc; i++) { const NodeData &n = nd[i]; @@ -230,9 +233,28 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { for (int j = 0; j < nprop_count; j++) { bool valid; - ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); + ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr); + if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { + uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); + ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, just set the metadata and be it + node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]); + } else { + // Do an actual deferred sed of the property path. + DeferredNodePathProperties dnp; + dnp.path = props[nprops[j].value]; + dnp.base = node; + dnp.property = snames[name_idx]; + deferred_node_paths.push_back(dnp); + } + continue; + } + + ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); + if (snames[nprops[j].name] == CoreStringNames::get_singleton()->_script) { //work around to avoid old script variables from disappearing, should be the proper fix to: //https://github.com/godotengine/godot/issues/2958 @@ -369,6 +391,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } + for (uint32_t i = 0; i < deferred_node_paths.size(); i++) { + const DeferredNodePathProperties &dnp = deferred_node_paths[i]; + Node *other = dnp.base->get_node_or_null(dnp.path); + dnp.base->set(dnp.property, other); + } + for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) { E.value->setup_local_to_scene(); } @@ -532,6 +560,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (E.name == META_PROPERTY_MISSING_RESOURCES) { continue; // Ignore this property when packing. } + if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) { + continue; // do not save. + } // If instance or inheriting, not saving if property requested so. if (!states_stack.is_empty()) { @@ -542,8 +573,15 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has StringName name = E.name; Variant value = p_node->get(name); + bool use_deferred_node_path_bit = false; - if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) { + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + value = p_node->get(META_POINTER_PROPERTY_BASE + E.name); + if (value.get_type() != Variant::NODE_PATH) { + continue; //was never set, ignore. + } + use_deferred_node_path_bit = true; + } else if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) { // Was this missing resource overridden? If so do not save the old value. Ref<Resource> ures = value; if (ures.is_null()) { @@ -562,6 +600,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has NodeData::Property prop; prop.name = _nm_get_string(name, name_map); prop.value = _vm_get_variant(value, variant_map); + if (use_deferred_node_path_bit) { + prop.name |= FLAG_PATH_PROPERTY_IS_NODE; + } nd.properties.push_back(prop); } @@ -1018,7 +1059,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property, const NodeData::Property *p = nodes[p_node].properties.ptr(); for (int i = 0; i < pc; i++) { - if (p_property == namep[p[i].name]) { + if (p_property == namep[p[i].name & FLAG_PROP_NAME_MASK]) { found = true; return variants[p[i].value]; } @@ -1409,7 +1450,19 @@ int SceneState::get_node_property_count(int p_idx) const { StringName SceneState::get_node_property_name(int p_idx, int p_prop) const { ERR_FAIL_INDEX_V(p_idx, nodes.size(), StringName()); ERR_FAIL_INDEX_V(p_prop, nodes[p_idx].properties.size(), StringName()); - return names[nodes[p_idx].properties[p_prop].name]; + return names[nodes[p_idx].properties[p_prop].name & FLAG_PROP_NAME_MASK]; +} + +Vector<String> SceneState::get_node_deferred_nodepath_properties(int p_idx) const { + Vector<String> ret; + ERR_FAIL_INDEX_V(p_idx, nodes.size(), ret); + for (int i = 0; i < nodes[p_idx].properties.size(); i++) { + uint32_t idx = nodes[p_idx].properties[i].name; + if (idx & FLAG_PATH_PROPERTY_IS_NODE) { + ret.push_back(names[idx & FLAG_PROP_NAME_MASK]); + } + } + return ret; } Variant SceneState::get_node_property_value(int p_idx, int p_prop) const { @@ -1555,13 +1608,16 @@ int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int return nodes.size() - 1; } -void SceneState::add_node_property(int p_node, int p_name, int p_value) { +void SceneState::add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path) { ERR_FAIL_INDEX(p_node, nodes.size()); ERR_FAIL_INDEX(p_name, names.size()); ERR_FAIL_INDEX(p_value, variants.size()); NodeData::Property prop; prop.name = p_name; + if (p_deferred_node_path) { + prop.name |= FLAG_PATH_PROPERTY_IS_NODE; + } prop.value = p_value; nodes.write[p_node].properties.push_back(prop); } @@ -1599,6 +1655,10 @@ void SceneState::add_editable_instance(const NodePath &p_path) { editable_instances.push_back(p_path); } +String SceneState::get_meta_pointer_property(const String &p_property) { + return META_POINTER_PROPERTY_BASE + p_property; +} + Vector<String> SceneState::_get_node_groups(int p_idx) const { Vector<StringName> groups = get_node_groups(p_idx); Vector<String> ret; diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 05abb23284..5f8001c871 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -69,6 +69,12 @@ class SceneState : public RefCounted { Vector<int> groups; }; + struct DeferredNodePathProperties { + Node *base = nullptr; + StringName property; + NodePath path; + }; + Vector<NodeData> nodes; struct ConnectionData { @@ -104,6 +110,8 @@ public: FLAG_ID_IS_PATH = (1 << 30), TYPE_INSTANCED = 0x7FFFFFFF, FLAG_INSTANCE_IS_PLACEHOLDER = (1 << 30), + FLAG_PATH_PROPERTY_IS_NODE = (1 << 30), + FLAG_PROP_NAME_MASK = FLAG_PATH_PROPERTY_IS_NODE - 1, FLAG_MASK = (1 << 24) - 1, }; @@ -157,6 +165,7 @@ public: int get_node_property_count(int p_idx) const; StringName get_node_property_name(int p_idx, int p_prop) const; Variant get_node_property_value(int p_idx, int p_prop) const; + Vector<String> get_node_deferred_nodepath_properties(int p_idx) const; int get_connection_count() const; NodePath get_connection_source(int p_idx) const; @@ -177,7 +186,7 @@ public: int add_value(const Variant &p_value); int add_node_path(const NodePath &p_path); int add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index); - void add_node_property(int p_node, int p_name, int p_value); + void add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path = false); void add_node_group(int p_node, int p_group); void set_base_scene(int p_idx); void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds); @@ -186,6 +195,9 @@ public: virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; } uint64_t get_last_modified_time() const { return last_modified_time; } + // Used when saving pointers (saves a path property instead). + static String get_meta_pointer_property(const String &p_property); + SceneState(); }; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index b2fd8eb895..f4fd81b25f 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -439,12 +439,15 @@ void CapsuleMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); + + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } void CapsuleMesh::set_radius(const float p_radius) { radius = p_radius; if (radius > height * 0.5) { - radius = height * 0.5; + height = radius * 2.0; } _request_update(); } @@ -456,7 +459,7 @@ float CapsuleMesh::get_radius() const { void CapsuleMesh::set_height(const float p_height) { height = p_height; if (radius > height * 0.5) { - height = radius * 2; + radius = height * 0.5; } _request_update(); } diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index dba53338eb..66afb001fb 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -212,6 +212,15 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars type = SceneState::TYPE_INSTANCED; //no type? assume this was instantiated } + HashSet<StringName> path_properties; + + if (next_tag.fields.has("node_paths")) { + Vector<String> paths = next_tag.fields["node_paths"]; + for (int i = 0; i < paths.size(); i++) { + path_properties.insert(paths[i]); + } + } + if (next_tag.fields.has("instance")) { instance = packed_scene->get_state()->add_value(next_tag.fields["instance"]); @@ -276,9 +285,10 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars } if (!assign.is_empty()) { - int nameidx = packed_scene->get_state()->add_name(assign); + StringName assign_name = assign; + int nameidx = packed_scene->get_state()->add_name(assign_name); int valueidx = packed_scene->get_state()->add_value(value); - packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx); + packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx, path_properties.has(assign_name)); //it's assignment } else if (!next_tag.name.is_empty()) { break; @@ -526,9 +536,9 @@ Error ResourceLoaderText::load() { if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { //reuse existing - Resource *r = ResourceCache::get(path); - if (r && r->get_class() == type) { - res = Ref<Resource>(r); + Ref<Resource> cache = ResourceCache::get_ref(path); + if (cache.is_valid() && cache->get_class() == type) { + res = cache; res->reset_state(); do_assign = true; } @@ -537,10 +547,10 @@ Error ResourceLoaderText::load() { MissingResource *missing_resource = nullptr; if (res.is_null()) { //not reuse - if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && ResourceCache::has(path)) { //only if it doesn't exist + Ref<Resource> cache = ResourceCache::get_ref(path); + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && cache.is_valid()) { //only if it doesn't exist //cached, do not assign - Resource *r = ResourceCache::get(path); - res = Ref<Resource>(r); + res = cache; } else { //create @@ -650,12 +660,10 @@ Error ResourceLoaderText::load() { return error; } - if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(local_path)) { - Resource *r = ResourceCache::get(local_path); - if (r->get_class() == res_type) { - r->reset_state(); - resource = Ref<Resource>(r); - } + Ref<Resource> cache = ResourceCache::get_ref(local_path); + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && cache.is_valid() && cache->get_class() == res_type) { + cache->reset_state(); + resource = cache; } MissingResource *missing_resource = nullptr; @@ -1941,6 +1949,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso Ref<PackedScene> instance = state->get_node_instance(i); String instance_placeholder = state->get_node_instance_placeholder(i); Vector<StringName> groups = state->get_node_groups(i); + Vector<String> deferred_node_paths = state->get_node_deferred_nodepath_properties(i); String header = "[node"; header += " name=\"" + String(name).c_escape() + "\""; @@ -1957,6 +1966,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso header += " index=\"" + itos(index) + "\""; } + if (deferred_node_paths.size()) { + header += " node_paths=" + Variant(deferred_node_paths).get_construct_string(); + } + if (groups.size()) { // Write all groups on the same line as they're part of a section header. // This improves readability while not impacting VCS friendliness too much, diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp index ac60dc3b9a..8560d92aa2 100644 --- a/servers/movie_writer/movie_writer.cpp +++ b/servers/movie_writer/movie_writer.cpp @@ -107,7 +107,7 @@ void MovieWriter::begin(const Size2i &p_movie_size, uint32_t p_fps, const String AudioDriverDummy::get_dummy_singleton()->set_speaker_mode(AudioDriver::SpeakerMode(get_audio_speaker_mode())); fps = p_fps; if ((mix_rate % fps) != 0) { - WARN_PRINT("Audio mix rate (" + itos(mix_rate) + ") can not be divided by fps (" + itos(fps) + "). Audio may go out of sync over time."); + WARN_PRINT("MovieWriter's audio mix rate (" + itos(mix_rate) + ") can not be divided by the recording FPS (" + itos(fps) + "). Audio may go out of sync over time."); } audio_channels = AudioDriverDummy::get_dummy_singleton()->get_channels(); @@ -128,15 +128,17 @@ void MovieWriter::_bind_methods() { GDVIRTUAL_BIND(_write_frame, "frame_image", "audio_frame_block") GDVIRTUAL_BIND(_write_end) - GLOBAL_DEF("editor/movie_writer/mix_rate_hz", 48000); + GLOBAL_DEF("editor/movie_writer/mix_rate", 48000); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/mix_rate", PropertyInfo(Variant::INT, "editor/movie_writer/mix_rate", PROPERTY_HINT_RANGE, "8000,192000,1,suffix:Hz")); GLOBAL_DEF("editor/movie_writer/speaker_mode", 0); ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/speaker_mode", PropertyInfo(Variant::INT, "editor/movie_writer/speaker_mode", PROPERTY_HINT_ENUM, "Stereo,3.1,5.1,7.1")); GLOBAL_DEF("editor/movie_writer/mjpeg_quality", 0.75); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/mjpeg_quality", PropertyInfo(Variant::FLOAT, "editor/movie_writer/mjpeg_quality", PROPERTY_HINT_RANGE, "0.01,1.0,0.01")); // used by the editor GLOBAL_DEF_BASIC("editor/movie_writer/movie_file", ""); GLOBAL_DEF_BASIC("editor/movie_writer/disable_vsync", false); GLOBAL_DEF_BASIC("editor/movie_writer/fps", 60); - ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/fps", PropertyInfo(Variant::INT, "editor/movie_writer/fps", PROPERTY_HINT_RANGE, "1,300,1")); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/fps", PropertyInfo(Variant::INT, "editor/movie_writer/fps", PROPERTY_HINT_RANGE, "1,300,1,suffix:FPS")); } void MovieWriter::set_extensions_hint() { @@ -301,6 +303,6 @@ void MovieWriterPNGWAV::write_end() { } MovieWriterPNGWAV::MovieWriterPNGWAV() { - mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate_hz"); + mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate"); speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode"))); } diff --git a/servers/movie_writer/movie_writer_mjpeg.cpp b/servers/movie_writer/movie_writer_mjpeg.cpp index b0c65e768d..7d9e6e3b87 100644 --- a/servers/movie_writer/movie_writer_mjpeg.cpp +++ b/servers/movie_writer/movie_writer_mjpeg.cpp @@ -257,7 +257,7 @@ void MovieWriterMJPEG::write_end() { } MovieWriterMJPEG::MovieWriterMJPEG() { - mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate_hz"); + mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate"); speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode"))); quality = GLOBAL_GET("editor/movie_writer/mjpeg_quality"); } diff --git a/servers/rendering/renderer_rd/effects/copy_effects.cpp b/servers/rendering/renderer_rd/effects/copy_effects.cpp index aa42643699..c30e8ed58f 100644 --- a/servers/rendering/renderer_rd/effects/copy_effects.cpp +++ b/servers/rendering/renderer_rd/effects/copy_effects.cpp @@ -1029,7 +1029,7 @@ void CopyEffects::cubemap_roughness(RID p_source_rd_texture, RID p_dest_texture, RD::Uniform u_source_rd_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_mipmap_sampler, p_source_rd_texture })); RD::Uniform u_dest_texture(RD::UNIFORM_TYPE_IMAGE, 0, Vector<RID>({ p_dest_texture })); - RID shader = roughness.compute_shader.version_get_shader(filter.shader_version, 0); + RID shader = roughness.compute_shader.version_get_shader(roughness.shader_version, 0); ERR_FAIL_COND(shader.is_null()); RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); @@ -1070,7 +1070,7 @@ void CopyEffects::cubemap_roughness_raster(RID p_source_rd_texture, RID p_dest_f RD::Uniform u_source_rd_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, p_source_rd_texture })); - RID shader = roughness.raster_shader.version_get_shader(filter.shader_version, 0); + RID shader = roughness.raster_shader.version_get_shader(roughness.shader_version, 0); ERR_FAIL_COND(shader.is_null()); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dest_framebuffer, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); |