Merge pull request #79297 from YuriSizov/4.1-cherrypicks

Cherry-picks for the 4.1 branch (future 4.1.1) - 1st batch
This commit is contained in:
Yuri Sizov 2023-07-11 15:55:57 +02:00 committed by GitHub
commit e709ad4d64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 876 additions and 832 deletions

View File

@ -4,6 +4,164 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [4.1.1] - TBD
See the [release announcement](https://godotengine.org/article/maintenance-release-godot-4-1-1) for details.
### Added
#### Animation
- Add 3.x compatibility for animation loop mode ([GH-79155](https://github.com/godotengine/godot/pull/79155)).
#### GUI
- Add compatibility properties to `TouchScreenButton` ([GH-78940](https://github.com/godotengine/godot/pull/78940)).
### Changed
#### 2D
- Improve string drawing in the tiledata editor ([GH-78522](https://github.com/godotengine/godot/pull/78522)).
- Make sure the shortcut key respects the context in `TileSetAtlasSourceEditor` ([GH-78920](https://github.com/godotengine/godot/pull/78920)).
#### Buildsystem
- Linux: Allow unbundling brotli to use system library ([GH-79101](https://github.com/godotengine/godot/pull/79101)).
- Linux: Link libsquish directly when unbundling, .pc file unreliable ([GH-79105](https://github.com/godotengine/godot/pull/79105)).
#### C#/.NET
- Update the RiderPathLocator to support the JetBrains Toolbox 2.0 ([GH-78832](https://github.com/godotengine/godot/pull/78832)).
- Compare symbol names without null flow state ([GH-79094](https://github.com/godotengine/godot/pull/79094)).
- Add null check before calling `UnregisterGodotObject` ([GH-79151](https://github.com/godotengine/godot/pull/79151)).
#### Core
- Check parameter validity in `Object::set_script` ([GH-46125](https://github.com/godotengine/godot/pull/46125)).
- Improve error message for `Node.set_owner` ([GH-79000](https://github.com/godotengine/godot/pull/79000)).
#### Editor
- Focus current node after connecting ([GH-54071](https://github.com/godotengine/godot/pull/54071)).
- Fix tooltip of enum value without description ([GH-78524](https://github.com/godotengine/godot/pull/78524)).
- Use bullet points in shader editor creation dialog ([GH-78631](https://github.com/godotengine/godot/pull/78631)).
- Sort project tags before saving ([GH-78775](https://github.com/godotengine/godot/pull/78775)).
- Project converter: Use same rendering driver as Project Manager ([GH-78795](https://github.com/godotengine/godot/pull/78795)).
- Add tooltip description wrapping in scene tree and plugin settings ([GH-79090](https://github.com/godotengine/godot/pull/79090)).
- Improve user-friendliness of project version mismatch messages ([GH-79118](https://github.com/godotengine/godot/pull/79118), [GH-79299](https://github.com/godotengine/godot/pull/79299)).
#### GUI
- Ensure that `_drop_physics_mouseover` only happens when necessary ([GH-78078](https://github.com/godotengine/godot/pull/78078)).
- Update FileDialog button activity when `file_mode` is changed ([GH-79211](https://github.com/godotengine/godot/pull/79211)).
- Hide/show `AcceptDialog`'s button spacer on button visibility changed ([GH-79274](https://github.com/godotengine/godot/pull/79274)).
#### Porting
- macOS/iOS: Set MoltenVK logging level based on `--verbose` flag ([GH-79061](https://github.com/godotengine/godot/pull/79061)).
- Windows: Flash both the window caption and taskbar button on `request_attention` ([GH-78263](https://github.com/godotengine/godot/pull/78263)).
#### Rendering
- Clear specular buffer if sky mode is canvas and screen space effects are used ([GH-78624](https://github.com/godotengine/godot/pull/78624)).
- Take eye offset into account for depth in StandardMaterial3D ([GH-79049](https://github.com/godotengine/godot/pull/79049)).
### Fixed
#### 2D
- Fix `Camera2D.rotating` not being converted and reversed properly ([GH-79264](https://github.com/godotengine/godot/pull/79264)).
#### 3D
- Fix Camera3D `project_*` methods not accounting for frustum offset ([GH-75806](https://github.com/godotengine/godot/pull/75806)).
- Fix 3D viewport grid disappearing on scene tab changes ([GH-78694](https://github.com/godotengine/godot/pull/78694)).
#### Animation
- Fix infinite loop state check in `AnimationStateMachine` ([GH-79141](https://github.com/godotengine/godot/pull/79141)).
#### Buildsystem
- Linux: Fix build with `use_sowrap=no` and various warnings/errors ([GH-79097](https://github.com/godotengine/godot/pull/79097)).
#### C#/.NET
- Fix command line exporting ([GH-79173](https://github.com/godotengine/godot/pull/79173)).
#### Core
- Fix zero-sized WorkerThreadPool not processing group tasks ([GH-78845](https://github.com/godotengine/godot/pull/78845)).
- Fix `Node::add_sibling` parent check ([GH-78847](https://github.com/godotengine/godot/pull/78847)).
- Fix error when non-ASCII characters in resource pack path ([GH-78935](https://github.com/godotengine/godot/pull/78935)).
- Fix erroneous `pad_zeros()` warning ([GH-79202](https://github.com/godotengine/godot/pull/79202)).
- Fix `PackedScene::get_last_modified_time()` always returns `0` ([GH-79237](https://github.com/godotengine/godot/pull/79237)).
#### Editor
- Fix dropping files from `res://` to `res://` ([GH-78914](https://github.com/godotengine/godot/pull/78914)).
- Do not change a node unique name to the same name ([GH-78925](https://github.com/godotengine/godot/pull/78925)).
- Collapse bottom panel if there is no active tab ([GH-79078](https://github.com/godotengine/godot/pull/79078)).
#### Export
- Fix export options of scripted `EditorExportPlugin`s ([GH-79025](https://github.com/godotengine/godot/pull/79025)).
#### GDScript
- Fix regression with GDScript enum descriptions now showing up in documentation ([GH-78953](https://github.com/godotengine/godot/pull/78953)).
#### GUI
- Fix cursor behavior for multiselect in Tree while holding CTRL ([GH-71024](https://github.com/godotengine/godot/pull/71024)).
- Fix disabled slider highlighting ([GH-78776](https://github.com/godotengine/godot/pull/78776)).
- Fix invalid minimum size for translated messages in option button ([GH-78835](https://github.com/godotengine/godot/pull/78835)).
- Fix incorrect property names in `FontFile::_get_property_list()` ([GH-78907](https://github.com/godotengine/godot/pull/78907)).
- Revert "Fix focusloss of non-exclusive `AcceptDialog` with `close_on_escape`" ([GH-79084](https://github.com/godotengine/godot/pull/79084)).
#### Import
- Fix property hint class name type string restriction and replace mode ([GH-79139](https://github.com/godotengine/godot/pull/79139)).
#### Navigation
- Fix closest possible navigation path position ([GH-79004](https://github.com/godotengine/godot/pull/79004)).
#### Networking
- Fix `rpc` calls with binds ([GH-78551](https://github.com/godotengine/godot/pull/78551)).
#### Particles
- Initialize particles instance buffer in case it is used before being updated ([GH-78852](https://github.com/godotengine/godot/pull/78852)).
- Unify error condition for particles trail lifetime ([GH-79270](https://github.com/godotengine/godot/pull/79270)).
#### Physics
- Fix rigid body `contact_monitor` property description ([GH-79250](https://github.com/godotengine/godot/pull/79250)).
#### Porting
- Fix formatting of `dlopen` error messages ([GH-78802](https://github.com/godotengine/godot/pull/78802)).
- Fix the fallback logic of `OS::shell_show_in_file_manager` ([GH-79087](https://github.com/godotengine/godot/pull/79087)).
- Linux/BSD: Avoid freeze when interacting with menus on Wayland ([GH-79143](https://github.com/godotengine/godot/pull/79143)).
- Linux/BSD: Fix `move_to_trash` wrongly reporting files as not found ([GH-79284](https://github.com/godotengine/godot/pull/79284)).
- Windows: Fix setting initial non-exclusive window mode ([GH-79016](https://github.com/godotengine/godot/pull/79016)).
#### Rendering
- Fix threading bug in Vulkan rendering device ([GH-78794](https://github.com/godotengine/godot/pull/78794)).
- Fix sanitizers reports about octahedral tangents in RenderingServer ([GH-78902](https://github.com/godotengine/godot/pull/78902)).
#### Shaders
- Fix invalid shader compilation when using `hint_normal_roughness_texture` in mobile backend ([GH-78839](https://github.com/godotengine/godot/pull/78839)).
- Fix using uint suffix at the hex number declaration in shaders ([GH-78906](https://github.com/godotengine/godot/pull/78906)).
- Fix shader language float literal precision truncation ([GH-78972](https://github.com/godotengine/godot/pull/78972)).
- Fix comments and indentation in `.gdshaderinc` files ([GH-79158](https://github.com/godotengine/godot/pull/79158)).
## [4.1] - 2023-07-06
See the [release announcement](https://godotengine.org/article/godot-4-1-is-here) for details.
@ -2876,6 +3034,7 @@ See the [release announcement](https://godotengine.org/article/godot-3-3-has-arr
- Only WebAssembly is supported now, since all browsers supporting WebGL 2.0 also support WebAssembly.
[4.1.1]: https://github.com/godotengine/godot/compare/4.1-stable...4.1.1-stable
[4.1]: https://github.com/godotengine/godot/compare/4.0-stable...4.1-stable
[4.0]: https://github.com/godotengine/godot/compare/3.2-stable...4.0-stable
[3.5]: https://github.com/godotengine/godot/compare/3.4-stable...3.5-stable

View File

@ -222,6 +222,7 @@ opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise e
opts.Add(BoolVariable("scu_build", "Use single compilation unit build", False))
# Thirdparty libraries
opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True))
opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True))
opts.Add(BoolVariable("builtin_embree", "Use the built-in Embree library", True))
opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet library", True))
@ -296,21 +297,21 @@ else:
if selected_platform in ["macos", "osx"]:
if selected_platform == "osx":
# Deprecated alias kept for compatibility.
print('Platform "osx" has been renamed to "macos" in Godot 4.0. Building for platform "macos".')
print('Platform "osx" has been renamed to "macos" in Godot 4. Building for platform "macos".')
# Alias for convenience.
selected_platform = "macos"
if selected_platform in ["ios", "iphone"]:
if selected_platform == "iphone":
# Deprecated alias kept for compatibility.
print('Platform "iphone" has been renamed to "ios" in Godot 4.0. Building for platform "ios".')
print('Platform "iphone" has been renamed to "ios" in Godot 4. Building for platform "ios".')
# Alias for convenience.
selected_platform = "ios"
if selected_platform in ["linux", "bsd", "x11"]:
if selected_platform == "x11":
# Deprecated alias kept for compatibility.
print('Platform "x11" has been renamed to "linuxbsd" in Godot 4.0. Building for platform "linuxbsd".')
print('Platform "x11" has been renamed to "linuxbsd" in Godot 4. Building for platform "linuxbsd".')
# Alias for convenience.
selected_platform = "linuxbsd"

View File

@ -65,7 +65,7 @@ thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_mis
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
# Brotli
if env["brotli"]:
if env["brotli"] and env["builtin_brotli"]:
thirdparty_brotli_dir = "#thirdparty/brotli/"
thirdparty_brotli_sources = [
"common/constants.c",

View File

@ -1192,7 +1192,7 @@ static const char *_joy_button_descriptions[(size_t)JoyButton::SDL_MAX] = {
TTRC("Top Action, Sony Triangle, Xbox Y, Nintendo X"),
TTRC("Back, Sony Select, Xbox Back, Nintendo -"),
TTRC("Guide, Sony PS, Xbox Home"),
TTRC("Start, Nintendo +"),
TTRC("Start, Xbox Menu, Nintendo +"),
TTRC("Left Stick, Sony L3, Xbox L/LS"),
TTRC("Right Stick, Sony R3, Xbox R/RS"),
TTRC("Left Shoulder, Sony L1, Xbox LB"),

View File

@ -35,13 +35,13 @@
#include "thirdparty/misc/fastlz.h"
#ifdef BROTLI_ENABLED
#include "thirdparty/brotli/include/brotli/decode.h"
#endif
#include <zlib.h>
#include <zstd.h>
#ifdef BROTLI_ENABLED
#include <brotli/decode.h>
#endif
int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode) {
switch (p_mode) {
case MODE_BROTLI: {

View File

@ -47,7 +47,7 @@ static void *godot_open(voidpf opaque, const char *p_fname, int mode) {
return nullptr;
}
Ref<FileAccess> f = FileAccess::open(p_fname, FileAccess::READ);
Ref<FileAccess> f = FileAccess::open(String::utf8(p_fname), FileAccess::READ);
ERR_FAIL_COND_V(f.is_null(), nullptr);
ZipData *zd = memnew(ZipData);

View File

@ -836,14 +836,16 @@ void Object::set_script(const Variant &p_script) {
return;
}
Ref<Script> s = p_script;
ERR_FAIL_COND_MSG(s.is_null() && !p_script.is_null(), "Invalid parameter, it should be a reference to a valid script (or null).");
script = p_script;
if (script_instance) {
memdelete(script_instance);
script_instance = nullptr;
}
script = p_script;
Ref<Script> s = script;
if (!s.is_null()) {
if (s->can_instantiate()) {
OBJ_DEBUG_LOCK

View File

@ -416,7 +416,7 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
ERR_FAIL_COND_V(p_elements < 0, INVALID_TASK_ID);
if (p_tasks < 0) {
p_tasks = threads.size();
p_tasks = MAX(1u, threads.size());
}
task_mutex.lock();

View File

@ -295,12 +295,14 @@ Error OS::shell_open(String p_uri) {
}
Error OS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
if (!p_path.begins_with("file://")) {
p_path = String("file://") + p_path;
}
if (!p_path.ends_with("/")) {
p_path = p_path.trim_prefix("file://");
if (!DirAccess::dir_exists_absolute(p_path)) {
p_path = p_path.get_base_dir();
}
p_path = String("file://") + p_path;
return shell_open(p_path);
}
// implement these with the canvas?

View File

@ -4281,12 +4281,13 @@ String String::pad_zeros(int p_digits) const {
begin++;
}
if (begin >= end) {
return s;
}
int zeros_to_add = p_digits - (end - begin);
return s.insert(begin, String("0").repeat(zeros_to_add));
if (zeros_to_add <= 0) {
return s;
} else {
return s.insert(begin, String("0").repeat(zeros_to_add));
}
}
String String::trim_prefix(const String &p_prefix) const {

View File

@ -144,6 +144,18 @@ void CallableCustomBind::call(const Variant **p_arguments, int p_argcount, Varia
callable.callp(args, p_argcount + binds.size(), r_return_value, r_call_error);
}
Error CallableCustomBind::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
const Variant **args = (const Variant **)alloca(sizeof(const Variant **) * (binds.size() + p_argcount));
for (int i = 0; i < p_argcount; i++) {
args[i] = (const Variant *)p_arguments[i];
}
for (int i = 0; i < binds.size(); i++) {
args[i + p_argcount] = (const Variant *)&binds[i];
}
return callable.rpcp(p_peer_id, args, p_argcount + binds.size(), r_call_error);
}
CallableCustomBind::CallableCustomBind(const Callable &p_callable, const Vector<Variant> &p_binds) {
callable = p_callable;
binds = p_binds;
@ -242,6 +254,16 @@ void CallableCustomUnbind::call(const Variant **p_arguments, int p_argcount, Var
callable.callp(p_arguments, p_argcount - argcount, r_return_value, r_call_error);
}
Error CallableCustomUnbind::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
if (argcount > p_argcount) {
r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_call_error.argument = 0;
r_call_error.expected = argcount;
return ERR_UNCONFIGURED;
}
return callable.rpcp(p_peer_id, p_arguments, p_argcount - argcount, r_call_error);
}
CallableCustomUnbind::CallableCustomUnbind(const Callable &p_callable, int p_argcount) {
callable = p_callable;
argcount = p_argcount;

View File

@ -51,6 +51,7 @@ public:
virtual StringName get_method() const override;
virtual ObjectID get_object() const override;
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
virtual const Callable *get_base_comparator() const override;
virtual int get_bound_arguments_count() const override;
virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const override;
@ -78,6 +79,7 @@ public:
virtual StringName get_method() const override;
virtual ObjectID get_object() const override;
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
virtual const Callable *get_base_comparator() const override;
virtual int get_bound_arguments_count() const override;
virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const override;

View File

@ -4,7 +4,7 @@
Blends two of three animations additively inside of an [AnimationNodeBlendTree].
</brief_description>
<description>
A resource to add to an [AnimationNodeBlendTree]. Blends two animations out of three additively out of three based on the amounmt value.
A resource to add to an [AnimationNodeBlendTree]. Blends two animations out of three additively out of three based on the amount value.
This animation node has three inputs:
- The base animation to add to
- A "-add" animation to blend with when the blend amount is negative

View File

@ -4,7 +4,7 @@
Blends two of three animations linearly inside of an [AnimationNodeBlendTree].
</brief_description>
<description>
A resource to add to an [AnimationNodeBlendTree]. Blends two animations out of three linearly out of three based on the amounmt value.
A resource to add to an [AnimationNodeBlendTree]. Blends two animations out of three linearly out of three based on the amount value.
This animation node has three inputs:
- The base animation to blend with
- A "-blend" animation to blend with when the blend amount is negative value

View File

@ -39,7 +39,7 @@
<return type="void" />
<param index="0" name="use_external_skeleton" type="bool" />
<description>
Sets whether the BoneAttachment3D node will use an extenral [Skeleton3D] node rather than attenpting to use its parent node as the [Skeleton3D]. When set to [code]true[/code], the BoneAttachment3D node will use the external [Skeleton3D] node set in [method set_external_skeleton].
Sets whether the BoneAttachment3D node will use an external [Skeleton3D] node rather than attempting to use its parent node as the [Skeleton3D]. When set to [code]true[/code], the BoneAttachment3D node will use the external [Skeleton3D] node set in [method set_external_skeleton].
</description>
</method>
</methods>

View File

@ -17,6 +17,12 @@
If this is the current camera, remove it from being current. If [param enable_next] is [code]true[/code], request to make the next camera current, if any.
</description>
</method>
<method name="get_camera_projection" qualifiers="const">
<return type="Projection" />
<description>
Returns the projection matrix that this camera uses to render to its associated viewport. The camera must be part of the scene tree to function.
</description>
</method>
<method name="get_camera_rid" qualifiers="const">
<return type="RID" />
<description>

View File

@ -130,7 +130,8 @@
<param index="1" name="file" type="PackedByteArray" />
<param index="2" name="remap" type="bool" />
<description>
Adds a custom file to be exported. [param path] is the virtual path that can be used to load the file, [param file] is the binary data of the file. If [param remap] is [code]true[/code], file will not be exported, but instead remapped to the given [param path].
Adds a custom file to be exported. [param path] is the virtual path that can be used to load the file, [param file] is the binary data of the file.
When called inside [method _export_file] and [param remap] is [code]true[/code], the current file will not be exported, but instead remapped to this custom file. [param remap] is ignored when called in other places.
</description>
</method>
<method name="add_ios_bundle_file">

View File

@ -12,20 +12,20 @@
<method name="get_architecture_name" qualifiers="const">
<return type="String" />
<description>
Returns the name of the CPU architecture the Godot binary was built for. Possible return values are [code]x86_64[/code], [code]x86_32[/code], [code]arm64[/code], [code]armv7[/code], [code]rv64[/code], [code]riscv[/code], [code]ppc64[/code], [code]ppc[/code], [code]wasm64[/code] and [code]wasm32[/code].
Returns the name of the CPU architecture the Godot binary was built for. Possible return values are [code]x86_64[/code], [code]x86_32[/code], [code]arm64[/code], [code]arm32[/code], [code]rv64[/code], [code]riscv[/code], [code]ppc64[/code], [code]ppc[/code], [code]wasm64[/code] and [code]wasm32[/code].
To detect whether the current CPU architecture is 64-bit, you can use the fact that all 64-bit architecture names have [code]64[/code] in their name:
[codeblocks]
[gdscript]
if "64" in Engine.get_architecture_name():
print("Running on 64-bit CPU.")
print("Running a 64-bit build of Godot.")
else:
print("Running on 32-bit CPU.")
print("Running a 32-bit build of Godot.")
[/gdscript]
[csharp]
if (Engine.GetArchitectureName().Contains("64"))
GD.Print("Running on 64-bit CPU.");
GD.Print("Running a 64-bit build of Godot.");
else
GD.Print("Running on 32-bit CPU.");
GD.Print("Running a 32-bit build of Godot.");
[/csharp]
[/codeblocks]
[b]Note:[/b] [method get_architecture_name] does [i]not[/i] return the name of the host CPU architecture. For example, if running an x86_32 Godot binary on a x86_64 system, the returned value will be [code]x86_32[/code].

View File

@ -103,7 +103,7 @@
The maximum distance the detail mesh surface should deviate from heightfield, in cell unit.
</member>
<member name="edge_max_error" type="float" setter="set_edge_max_error" getter="get_edge_max_error" default="1.3">
The maximum distance a simplfied contour's border edges should deviate the original raw contour.
The maximum distance a simplified contour's border edges should deviate the original raw contour.
</member>
<member name="edge_max_length" type="float" setter="set_edge_max_length" getter="get_edge_max_length" default="12.0">
The maximum allowed length for contour edges along the border of the mesh.

View File

@ -47,6 +47,7 @@
<description>
Parses the [SceneTree] for source geometry according to the properties of [param navigation_mesh]. Updates the provided [param source_geometry_data] resource with the resulting data. The resource can then be used to bake a navigation mesh with [method bake_from_source_geometry_data]. After the process is finished the optional [param callback] will be called.
[b]Note:[/b] This function needs to run on the main thread or with a deferred call as the SceneTree is not thread-safe.
[b]Performance:[/b] While convenient, reading data arrays from [Mesh] resources can affect the frame rate negatively. The data needs to be received from the GPU, stalling the [RenderingServer] in the process. For performance prefer the use of e.g. collision shapes or creating the data arrays entirely in code.
</description>
</method>
</methods>

View File

@ -685,6 +685,7 @@
<description>
Parses the [SceneTree] for source geometry according to the properties of [param navigation_mesh]. Updates the provided [param source_geometry_data] resource with the resulting data. The resource can then be used to bake a navigation mesh with [method bake_from_source_geometry_data]. After the process is finished the optional [param callback] will be called.
[b]Note:[/b] This function needs to run on the main thread or with a deferred call as the SceneTree is not thread-safe.
[b]Performance:[/b] While convenient, reading data arrays from [Mesh] resources can affect the frame rate negatively. The data needs to be received from the GPU, stalling the [RenderingServer] in the process. For performance prefer the use of e.g. collision shapes or creating the data arrays entirely in code.
</description>
</method>
<method name="query_path" qualifiers="const">

View File

@ -858,8 +858,8 @@
[b]Note:[/b] Auto-generated names might include the [code]@[/code] character, which is reserved for unique names when using [method add_child]. When setting the name manually, any [code]@[/code] will be removed.
</member>
<member name="owner" type="Node" setter="set_owner" getter="get_owner">
The node owner. A node can have any other node as owner (as long as it is a valid parent, grandparent, etc. ascending in the tree). When saving a node (using [PackedScene]), all the nodes it owns will be saved with it. This allows for the creation of complex [SceneTree]s, with instancing and subinstancing.
[b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]tool scripts[/url] and [url=$DOCS_URL/tutorials/plugins/editor/index.html]editor plugins[/url]. If [method add_child] is called without setting [member owner], the newly added [Node] will not be visible in the scene tree, though it will be visible in the 2D/3D view.
The node owner. A node can have any ancestor node as owner (i.e. a parent, grandparent, etc. node ascending in the tree). This implies that [method add_child] should be called before setting the owner, so that this relationship of parenting exists. When saving a node (using [PackedScene]), all the nodes it owns will be saved with it. This allows for the creation of complex scene trees, with instancing and subinstancing.
[b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]tool scripts[/url] and [url=$DOCS_URL/tutorials/plugins/editor/index.html]editor plugins[/url]. If a new node is added to the tree without setting its owner as an ancestor in that tree, it will be visible in the 2D/3D view, but not in the scene tree (and not persisted when packing or saving).
</member>
<member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Node.ProcessMode" default="0">
Can be used to pause or unpause the node, or make the node paused based on the [SceneTree], or make it inherit the process mode from its parent (default).

View File

@ -657,7 +657,7 @@
Requests the OS to open the file manager, then navigate to the given [param file_or_dir_path] and select the target file or folder.
If [param file_or_dir_path] is a valid directory path, and [param open_folder] is [code]true[/code], the method will open the file manager and enter the target folder without selecting anything.
Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] path into a system path for use with this method.
[b]Note:[/b] Currently this method is only implemented on Windows. On other platforms, it will fallback to [method shell_open] with a directory path for [param file_or_dir_path].
[b]Note:[/b] Currently this method is only implemented on Windows and macOS. On other platforms, it will fallback to [method shell_open] with a directory path of [param file_or_dir_path] with prefix [code]file://[/code].
</description>
</method>
<method name="unset_environment" qualifiers="const">

View File

@ -36,7 +36,7 @@
</member>
<member name="bake_simplification_distance" type="float" setter="set_bake_simplification_distance" getter="get_bake_simplification_distance" default="0.1">
The simplification distance to use for simplifying the generated occluder polygon (in 3D units). Higher values result in a less detailed occluder mesh, which improves performance but reduces culling accuracy.
The occluder geometry is rendered on the CPU, so it is important to keep its geometry as simple as possible. Since the buffer is rendered at a low resolution, less detailed occluder meshes generally still work well. The default value is fairly aggressive, so you may have to decrase it if you run into false negatives (objects being occluded even though they are visible by the camera). A value of [code]0.01[/code] will act conservatively, and will keep geometry [i]perceptually[/i] unaffected in the occlusion culling buffer. Depending on the scene, a value of [code]0.01[/code] may still simplify the mesh noticeably compared to disabling simplification entirely.
The occluder geometry is rendered on the CPU, so it is important to keep its geometry as simple as possible. Since the buffer is rendered at a low resolution, less detailed occluder meshes generally still work well. The default value is fairly aggressive, so you may have to decrease it if you run into false negatives (objects being occluded even though they are visible by the camera). A value of [code]0.01[/code] will act conservatively, and will keep geometry [i]perceptually[/i] unaffected in the occlusion culling buffer. Depending on the scene, a value of [code]0.01[/code] may still simplify the mesh noticeably compared to disabling simplification entirely.
Setting this to [code]0.0[/code] disables simplification entirely, but vertices in the exact same position will still be merged. The mesh will also be re-indexed to reduce both the number of vertices and indices.
[b]Note:[/b] This uses the [url=https://meshoptimizer.org/]meshoptimizer[/url] library under the hood, similar to LOD generation.
</member>

View File

@ -32,7 +32,7 @@
The index of the [Bone2D] that this [PhysicalBone2D] should simulate.
</member>
<member name="bone2d_nodepath" type="NodePath" setter="set_bone2d_nodepath" getter="get_bone2d_nodepath" default="NodePath(&quot;&quot;)">
The [NodePath] to the [Bone2D] that this [PhysicalBone2D] isshould simulate.
The [NodePath] to the [Bone2D] that this [PhysicalBone2D] should simulate.
</member>
<member name="follow_bone_when_simulating" type="bool" setter="set_follow_bone_when_simulating" getter="get_follow_bone_when_simulating" default="false">
If [code]true[/code], the [PhysicalBone2D] will keep the transform of the bone it is bound to when simulating physics.

View File

@ -151,7 +151,7 @@
See [method add_constant_torque].
</member>
<member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" default="false">
If [code]true[/code], the RigidBody2D will emit signals when it collides with another RigidBody2D.
If [code]true[/code], the RigidBody2D will emit signals when it collides with another body.
[b]Note:[/b] By default the maximum contacts reported is set to 0, meaning nothing will be recorded, see [member max_contacts_reported].
</member>
<member name="continuous_cd" type="int" setter="set_continuous_collision_detection_mode" getter="get_continuous_collision_detection_mode" enum="RigidBody2D.CCDMode" default="0">

View File

@ -158,7 +158,7 @@
See [method add_constant_torque].
</member>
<member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" default="false">
If [code]true[/code], the RigidBody3D will emit signals when it collides with another RigidBody3D.
If [code]true[/code], the RigidBody3D will emit signals when it collides with another body.
[b]Note:[/b] By default the maximum contacts reported is set to 0, meaning nothing will be recorded, see [member max_contacts_reported].
</member>
<member name="continuous_cd" type="bool" setter="set_use_continuous_collision_detection" getter="is_using_continuous_collision_detection" default="false">

View File

@ -920,11 +920,11 @@
<description>
Converts the string representing a decimal number into a [float]. This method stops on the first non-number character, except the first decimal point ([code].[/code]) and the exponent letter ([code]e[/code]). See also [method is_valid_float].
[codeblock]
var a = "12.35".to_float() # a is 12.35
var b = "1.2.3".to_float() # b is 1.2
var c = "12xy3".to_float() # c is 12.0
var d = "1e3".to_float() # d is 1000.0
var e = "Hello!".to_int() # e is 0.0
var a = "12.35".to_float() # a is 12.35
var b = "1.2.3".to_float() # b is 1.2
var c = "12xy3".to_float() # c is 12.0
var d = "1e3".to_float() # d is 1000.0
var e = "Hello!".to_float() # e is 0.0
[/codeblock]
</description>
</method>

View File

@ -29,15 +29,15 @@
<description>
Creates a TLS client configuration which validates certificates and their common names (fully qualified domain names).
You can specify a custom [param trusted_chain] of certification authorities (the default CA list will be used if [code]null[/code]), and optionally provide a [param common_name_override] if you expect the certificate to have a common name other then the server FQDN.
Note: On the Web plafrom, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
[b]Note:[/b] On the Web platform, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
</description>
</method>
<method name="client_unsafe" qualifiers="static">
<return type="TLSOptions" />
<param index="0" name="trusted_chain" type="X509Certificate" default="null" />
<description>
Creates an [b]unsafe[/b] TLS client configuration where certificate validation is optional. You can optionally provide a valid [param trusted_chain], but the common name of the certififcates will never be checked. Using this configuration for purposes other than testing [b]is not recommended[/b].
Note: On the Web plafrom, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
Creates an [b]unsafe[/b] TLS client configuration where certificate validation is optional. You can optionally provide a valid [param trusted_chain], but the common name of the certificates will never be checked. Using this configuration for purposes other than testing [b]is not recommended[/b].
[b]Note:[/b] On the Web platform, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
</description>
</method>
<method name="server" qualifiers="static">
@ -46,7 +46,7 @@
<param index="1" name="certificate" type="X509Certificate" />
<description>
Creates a TLS server configuration using the provided [param key] and [param certificate].
Note: The [param certificate] should include the full certificate chain up to the signing CA (certificates file can be concatenated using a general purpose text editor).
[b]Note:[/b] The [param certificate] should include the full certificate chain up to the signing CA (certificates file can be concatenated using a general purpose text editor).
</description>
</method>
</methods>

View File

@ -820,12 +820,11 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
particles->num_attrib_arrays_cache = 5 + userdata_count + (xform_size - 2);
particles->process_buffer_stride_cache = sizeof(float) * 4 * particles->num_attrib_arrays_cache;
int process_data_amount = 4 * particles->num_attrib_arrays_cache * total_amount;
float *data = memnew_arr(float, process_data_amount);
PackedByteArray data;
data.resize_zeroed(particles->process_buffer_stride_cache * total_amount);
for (int i = 0; i < process_data_amount; i++) {
data[i] = 0;
}
PackedByteArray instance_data;
instance_data.resize_zeroed(particles->instance_buffer_size_cache);
{
glGenVertexArrays(1, &particles->front_vertex_array);
@ -834,7 +833,7 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
glGenBuffers(1, &particles->front_instance_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles->front_process_buffer);
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->front_process_buffer, particles->process_buffer_stride_cache * total_amount, data, GL_DYNAMIC_COPY, "Particles front process buffer");
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->front_process_buffer, particles->process_buffer_stride_cache * total_amount, data.ptr(), GL_DYNAMIC_COPY, "Particles front process buffer");
for (uint32_t j = 0; j < particles->num_attrib_arrays_cache; j++) {
glEnableVertexAttribArray(j);
@ -843,7 +842,7 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, particles->front_instance_buffer);
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->front_instance_buffer, particles->instance_buffer_size_cache, nullptr, GL_DYNAMIC_COPY, "Particles front instance buffer");
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->front_instance_buffer, particles->instance_buffer_size_cache, instance_data.ptr(), GL_DYNAMIC_COPY, "Particles front instance buffer");
}
{
@ -853,7 +852,7 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
glGenBuffers(1, &particles->back_instance_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer);
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->back_process_buffer, particles->process_buffer_stride_cache * total_amount, data, GL_DYNAMIC_COPY, "Particles back process buffer");
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->back_process_buffer, particles->process_buffer_stride_cache * total_amount, data.ptr(), GL_DYNAMIC_COPY, "Particles back process buffer");
for (uint32_t j = 0; j < particles->num_attrib_arrays_cache; j++) {
glEnableVertexAttribArray(j);
@ -862,11 +861,9 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, particles->back_instance_buffer);
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->back_instance_buffer, particles->instance_buffer_size_cache, nullptr, GL_DYNAMIC_COPY, "Particles back instance buffer");
GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_ARRAY_BUFFER, particles->back_instance_buffer, particles->instance_buffer_size_cache, instance_data.ptr(), GL_DYNAMIC_COPY, "Particles back instance buffer");
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
memdelete_arr(data);
}
}

View File

@ -315,7 +315,7 @@ Dictionary OS_Unix::get_memory_info() const {
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
vm_statistics64_data_t vmstat;
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) {
ERR_PRINT(vformat("Could not get host vm statistics."));
ERR_PRINT("Could not get host vm statistics.");
}
struct xsw_usage swap_used;
len = sizeof(swap_used);
@ -647,7 +647,7 @@ Error OS_Unix::open_dynamic_library(const String p_path, void *&p_library_handle
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror());
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
*r_resolved_path = path;

View File

@ -7470,6 +7470,7 @@ uint32_t RenderingDeviceVulkan::draw_list_get_current_pass() {
}
RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_switch_to_next_pass() {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(draw_list == nullptr, INVALID_ID);
ERR_FAIL_COND_V(draw_list_current_subpass >= draw_list_subpass_count - 1, INVALID_FORMAT_ID);
@ -7485,6 +7486,7 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_switch_to_next_pass
return int64_t(ID_TYPE_DRAW_LIST) << ID_BASE_SHIFT;
}
Error RenderingDeviceVulkan::draw_list_switch_to_next_pass_split(uint32_t p_splits, DrawListID *r_split_ids) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(draw_list == nullptr, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(draw_list_current_subpass >= draw_list_subpass_count - 1, ERR_INVALID_PARAMETER);

View File

@ -1818,7 +1818,7 @@ Error VulkanContext::_update_swap_chain(Window *window) {
if (window->width == 0 || window->height == 0) {
free(presentModes);
// Likely window minimized, no swapchain created.
return OK;
return ERR_SKIP;
}
// The FIFO present mode is guaranteed by the spec to be supported
// and to have no tearing. It's a great default present mode to use.
@ -2275,8 +2275,10 @@ Error VulkanContext::prepare_buffers() {
// Swapchain is not as optimal as it could be, but the platform's
// presentation engine will still present the image correctly.
print_verbose("Vulkan: Early suboptimal swapchain, recreating.");
_update_swap_chain(w);
break;
Error swap_chain_err = _update_swap_chain(w);
if (swap_chain_err == ERR_SKIP) {
break;
}
} else if (err != VK_SUCCESS) {
ERR_BREAK_MSG(err != VK_SUCCESS, "Vulkan: Did not create swapchain successfully. Error code: " + String(string_VkResult(err)));
} else {

View File

@ -410,7 +410,7 @@ void FindReplaceBar::_update_matches_label() {
matches_label->add_theme_color_override("font_color", results_count > 0 ? get_theme_color(SNAME("font_color"), SNAME("Label")) : get_theme_color(SNAME("error_color"), SNAME("Editor")));
if (results_count == 0) {
matches_label->set_text("No match");
matches_label->set_text(TTR("No match"));
} else if (results_count_to_current == -1) {
matches_label->set_text(vformat(TTRN("%d match", "%d matches", results_count), results_count));
} else {

View File

@ -39,6 +39,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/node_dock.h"
#include "editor/scene_tree_dock.h"
#include "plugins/script_editor_plugin.h"
#include "scene/gui/button.h"
@ -1440,6 +1441,7 @@ ConnectionsDock::ConnectionsDock() {
connect_button->connect("pressed", callable_mp(this, &ConnectionsDock::_connect_pressed));
connect_dialog = memnew(ConnectDialog);
connect_dialog->connect("connected", callable_mp(NodeDock::get_singleton(), &NodeDock::restore_last_valid_node), CONNECT_DEFERRED);
add_child(connect_dialog);
disconnect_all_dialog = memnew(ConfirmationDialog);

View File

@ -1635,6 +1635,7 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path) {
}
void EditorFileSystem::update_file(const String &p_file) {
ERR_FAIL_COND(p_file.is_empty());
EditorFileSystemDirectory *fs = nullptr;
int cpos = -1;

View File

@ -1367,7 +1367,7 @@ void EditorHelp::_update_doc() {
class_desc->add_newline();
// Enum description.
if (e != "@unnamed_enums" && cd.enums.has(e)) {
if (e != "@unnamed_enums" && cd.enums.has(e) && !cd.enums[e].strip_edges().is_empty()) {
class_desc->push_color(theme_cache.text_color);
_push_normal_font();
class_desc->push_indent(1);

View File

@ -3184,10 +3184,11 @@ void EditorInspector::update_tree() {
if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
// Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
String desc = DTR(val.description).trim_prefix("\n");
doc_info.description += vformat(
"\n[b]%s:[/b] %s",
enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "),
DTR(val.description).trim_prefix("\n"));
desc.is_empty() ? ("[i]" + TTR("No description.") + "[/i]") : desc);
}
}
}

View File

@ -5275,21 +5275,29 @@ void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_
void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file) {
// Bottom panel.
if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "center_split_offset")) {
int center_split_offset = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset");
center_split->set_split_offset(center_split_offset);
}
bool has_active_tab = false;
if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item")) {
int selected_bottom_panel_item_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item");
if (selected_bottom_panel_item_idx >= 0 && selected_bottom_panel_item_idx < bottom_panel_items.size()) {
// Make sure we don't try to open contextual editors which are not enabled in the current context.
if (bottom_panel_items[selected_bottom_panel_item_idx].button->is_visible()) {
_bottom_panel_switch(true, selected_bottom_panel_item_idx);
has_active_tab = true;
}
}
}
if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "center_split_offset")) {
int center_split_offset = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset");
center_split->set_split_offset(center_split_offset);
// If there is no active tab we need to collapse the panel.
if (!has_active_tab) {
bottom_panel_items[0].control->show(); // _bottom_panel_switch() can collapse only visible tabs.
_bottom_panel_switch(false, 0);
}
}
// Debugger tab.
if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx")) {

View File

@ -101,9 +101,18 @@ void EditorPluginSettings::update_plugins() {
String description = cf->get_value("plugin", "description");
String scr = cf->get_value("plugin", "script");
const PackedInt32Array boundaries = TS->string_get_word_breaks(description, "", 80);
String wrapped_description;
for (int j = 0; j < boundaries.size(); j += 2) {
const int start = boundaries[j];
const int end = boundaries[j + 1];
wrapped_description += "\n" + description.substr(start, end - start + 1).rstrip("\n");
}
TreeItem *item = plugin_list->create_item(root);
item->set_text(0, name);
item->set_tooltip_text(0, TTR("Name:") + " " + name + "\n" + TTR("Path:") + " " + path + "\n" + TTR("Main Script:") + " " + scr + "\n" + TTR("Description:") + " " + description);
item->set_tooltip_text(0, TTR("Name:") + " " + name + "\n" + TTR("Path:") + " " + path + "\n" + TTR("Main Script:") + " " + scr + "\n" + TTR("Description:") + " " + wrapped_description);
item->set_metadata(0, path);
item->set_text(1, version);
item->set_metadata(1, scr);

View File

@ -621,7 +621,7 @@ void EditorPropertyClassName::update_property() {
}
void EditorPropertyClassName::_property_selected() {
dialog->popup_create(true);
dialog->popup_create(true, true, get_edited_property_value(), get_edited_property());
}
void EditorPropertyClassName::_dialog_created() {
@ -3622,7 +3622,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
return editor;
} else if (p_hint == PROPERTY_HINT_TYPE_STRING) {
EditorPropertyClassName *editor = memnew(EditorPropertyClassName);
editor->setup("Object", p_hint_text);
editor->setup(p_hint_text, p_hint_text);
return editor;
} else if (p_hint == PROPERTY_HINT_LOCALE_ID) {
EditorPropertyLocale *editor = memnew(EditorPropertyLocale);

View File

@ -90,11 +90,12 @@ void EditorExport::_save() {
String option_section = "preset." + itos(i) + ".options";
for (const PropertyInfo &E : preset->get_properties()) {
if (E.usage & PROPERTY_USAGE_SECRET) {
credentials->set_value(option_section, E.name, preset->get(E.name));
for (const KeyValue<StringName, Variant> &E : preset->values) {
PropertyInfo *prop = preset->properties.getptr(E.key);
if (prop && prop->usage & PROPERTY_USAGE_SECRET) {
credentials->set_value(option_section, E.key, E.value);
} else {
config->set_value(option_section, E.name, preset->get(E.name));
config->set_value(option_section, E.key, E.value);
}
}
}
@ -116,6 +117,7 @@ void EditorExport::_bind_methods() {
void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
export_platforms.push_back(p_platform);
should_update_presets = true;
}
int EditorExport::get_export_platform_count() {
@ -169,11 +171,13 @@ void EditorExport::remove_export_preset(int p_idx) {
void EditorExport::add_export_plugin(const Ref<EditorExportPlugin> &p_plugin) {
if (!export_plugins.has(p_plugin)) {
export_plugins.push_back(p_plugin);
should_update_presets = true;
}
}
void EditorExport::remove_export_plugin(const Ref<EditorExportPlugin> &p_plugin) {
export_plugins.erase(p_plugin);
should_update_presets = true;
}
Vector<Ref<EditorExportPlugin>> EditorExport::get_export_plugins() {
@ -314,8 +318,11 @@ void EditorExport::load_config() {
credentials->get_section_keys(option_section, &options);
for (const String &E : options) {
Variant value = credentials->get_value(option_section, E);
preset->set(E, value);
// Drop values for secret properties that no longer exist, or during the next save they would end up in the regular config file.
if (preset->get_properties().has(E)) {
Variant value = credentials->get_value(option_section, E);
preset->set(E, value);
}
}
}
@ -332,7 +339,8 @@ void EditorExport::update_export_presets() {
for (int i = 0; i < export_platforms.size(); i++) {
Ref<EditorExportPlatform> platform = export_platforms[i];
bool should_update = platform->should_update_export_options();
bool should_update = should_update_presets;
should_update |= platform->should_update_export_options();
for (int j = 0; j < export_plugins.size(); j++) {
should_update |= export_plugins.write[j]->_should_update_export_options(platform);
}
@ -342,12 +350,13 @@ void EditorExport::update_export_presets() {
platform->get_export_options(&options);
for (int j = 0; j < export_plugins.size(); j++) {
export_plugins.write[j]->_get_export_options(platform, &options);
export_plugins[j]->_get_export_options(platform, &options);
}
platform_options[platform->get_name()] = options;
}
}
should_update_presets = false;
bool export_presets_updated = false;
for (int i = 0; i < export_presets.size(); i++) {
@ -357,19 +366,16 @@ void EditorExport::update_export_presets() {
List<EditorExportPlatform::ExportOption> options = platform_options[preset->get_platform()->get_name()];
// Copy the previous preset values
HashMap<StringName, Variant> previous_values = preset->values;
// Clear the preset properties and values prior to reloading
// Clear the preset properties prior to reloading, keep the values to preserve options from plugins that may be currently disabled.
preset->properties.clear();
preset->values.clear();
preset->update_visibility.clear();
for (const EditorExportPlatform::ExportOption &E : options) {
preset->properties.push_back(E.option);
StringName option_name = E.option.name;
preset->values[option_name] = previous_values.has(option_name) ? previous_values[option_name] : E.default_value;
preset->properties[option_name] = E.option;
if (!preset->has(option_name)) {
preset->values[option_name] = E.default_value;
}
preset->update_visibility[option_name] = E.update_visibility;
}
}

View File

@ -45,6 +45,7 @@ class EditorExport : public Node {
Timer *save_timer = nullptr;
bool block_save = false;
bool should_update_presets = false;
static EditorExport *singleton;

View File

@ -329,9 +329,10 @@ Ref<EditorExportPreset> EditorExportPlatform::create_preset() {
}
for (const ExportOption &E : options) {
preset->properties.push_back(E.option);
preset->values[E.option.name] = E.default_value;
preset->update_visibility[E.option.name] = E.update_visibility;
StringName option_name = E.option.name;
preset->properties[option_name] = E.option;
preset->values[option_name] = E.default_value;
preset->update_visibility[option_name] = E.update_visibility;
}
return preset;

View File

@ -31,9 +31,9 @@
#include "editor_export.h"
bool EditorExportPreset::_set(const StringName &p_name, const Variant &p_value) {
if (values.has(p_name)) {
values[p_name] = p_value;
EditorExport::singleton->save_presets();
values[p_name] = p_value;
EditorExport::singleton->save_presets();
if (update_visibility.has(p_name)) {
if (update_visibility[p_name]) {
notify_property_list_changed();
}
@ -61,9 +61,9 @@ String EditorExportPreset::_get_property_warning(const StringName &p_name) const
}
void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const {
for (const PropertyInfo &E : properties) {
if (platform->get_export_option_visibility(this, E.name)) {
p_list->push_back(E);
for (const KeyValue<StringName, PropertyInfo> &E : properties) {
if (platform->get_export_option_visibility(this, E.key)) {
p_list->push_back(E.value);
}
}
}

View File

@ -70,7 +70,7 @@ private:
friend class EditorExport;
friend class EditorExportPlatform;
List<PropertyInfo> properties;
HashMap<StringName, PropertyInfo> properties;
HashMap<StringName, Variant> values;
HashMap<StringName, bool> update_visibility;
@ -154,7 +154,8 @@ public:
Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const;
const List<PropertyInfo> &get_properties() const { return properties; }
const HashMap<StringName, PropertyInfo> &get_properties() const { return properties; }
const HashMap<StringName, Variant> &get_values() const { return values; }
EditorExportPreset();
};

View File

@ -577,8 +577,8 @@ void ProjectExportDialog::_duplicate_preset() {
preset->set_exclude_filter(current->get_exclude_filter());
preset->set_custom_features(current->get_custom_features());
for (const PropertyInfo &E : current->get_properties()) {
preset->set(E.name, current->get(E.name));
for (const KeyValue<StringName, Variant> &E : current->get_values()) {
preset->set(E.key, E.value);
}
EditorExport::get_singleton()->add_export_preset(preset);

View File

@ -2617,8 +2617,10 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data,
if (!to_dir.is_empty()) {
Vector<String> fnames = drag_data["files"];
to_move.clear();
String target_dir = to_dir == "res://" ? to_dir : to_dir.trim_suffix("/");
for (int i = 0; i < fnames.size(); i++) {
if (fnames[i].trim_suffix("/").get_base_dir() != to_dir.trim_suffix("/")) {
if (fnames[i].trim_suffix("/").get_base_dir() != target_dir) {
to_move.push_back(FileOrFolder(fnames[i], !fnames[i].ends_with("/")));
}
}

View File

@ -374,7 +374,14 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) {
tooltip += String("\n" + TTR("Type:") + " " + (custom_type != StringName() ? String(custom_type) : p_node->get_class()));
if (!p_node->get_editor_description().is_empty()) {
tooltip += "\n\n" + p_node->get_editor_description();
const PackedInt32Array boundaries = TS->string_get_word_breaks(p_node->get_editor_description(), "", 80);
tooltip += "\n";
for (int i = 0; i < boundaries.size(); i += 2) {
const int start = boundaries[i];
const int end = boundaries[i + 1];
tooltip += "\n" + p_node->get_editor_description().substr(start, end - start + 1).rstrip("\n");
}
}
item->set_tooltip_text(0, tooltip);
@ -1019,11 +1026,18 @@ void SceneTreeEditor::_renamed() {
}
}
if (n->is_unique_name_in_owner() && get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name) != nullptr) {
error->set_text(TTR("Another node already uses this unique name in the scene."));
error->popup_centered();
which->set_text(0, n->get_name());
return;
if (n->is_unique_name_in_owner()) {
Node *existing = get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name);
if (existing == n) {
which->set_text(0, n->get_name());
return;
}
if (existing != nullptr) {
error->set_text(TTR("Another node already uses this unique name in the scene."));
error->popup_centered();
which->set_text(0, n->get_name());
return;
}
}
_rename_node(n, new_name);
@ -1100,9 +1114,15 @@ void SceneTreeEditor::_update_selection(TreeItem *item) {
}
if (editor_selection->is_selected(n)) {
item->select(0);
if (!item->is_selected(0)) {
item->select(0);
}
} else {
item->deselect(0);
if (item->is_selected(0)) {
TreeItem *previous_cursor_item = tree->get_selected();
item->deselect(0);
previous_cursor_item->set_as_cursor(0);
}
}
TreeItem *c = item->get_first_child();

View File

@ -70,6 +70,9 @@ void NodeDock::update_lists() {
void NodeDock::set_node(Node *p_node) {
connections->set_node(p_node);
groups->set_current(p_node);
if (p_node) {
last_valid_node = p_node;
}
if (p_node) {
if (connections_button->is_pressed()) {
@ -88,6 +91,10 @@ void NodeDock::set_node(Node *p_node) {
}
}
void NodeDock::restore_last_valid_node() {
set_node(last_valid_node);
}
NodeDock::NodeDock() {
singleton = this;

View File

@ -47,6 +47,7 @@ class NodeDock : public VBoxContainer {
HBoxContainer *mode_hb = nullptr;
Label *select_a_node = nullptr;
Node *last_valid_node = nullptr;
private:
static NodeDock *singleton;
@ -60,6 +61,7 @@ protected:
public:
void set_node(Node *p_node);
void restore_last_valid_node();
void show_groups();
void show_connections();

View File

@ -7925,6 +7925,8 @@ void Node3DEditor::clear() {
}
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true);
grid_enabled = true;
grid_init_draw = false;
}
void Node3DEditor::_sun_direction_draw() {

View File

@ -238,7 +238,7 @@ void ShaderTextEditor::_load_theme_settings() {
ShaderPreprocessor::get_keyword_list(&pp_keywords, false);
for (const String &E : pp_keywords) {
syntax_highlighter->add_keyword_color(E, keyword_color);
syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
}
// Colorize built-ins like `COLOR` differently to make them easier
@ -646,13 +646,13 @@ void TextShaderEditor::_menu_option(int p_option) {
shader_editor->move_lines_down();
} break;
case EDIT_INDENT: {
if (shader.is_null()) {
if (shader.is_null() && shader_inc.is_null()) {
return;
}
shader_editor->get_text_editor()->indent_lines();
} break;
case EDIT_UNINDENT: {
if (shader.is_null()) {
if (shader.is_null() && shader_inc.is_null()) {
return;
}
shader_editor->get_text_editor()->unindent_lines();
@ -668,12 +668,10 @@ void TextShaderEditor::_menu_option(int p_option) {
shader_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);
} break;
case EDIT_TOGGLE_COMMENT: {
if (shader.is_null()) {
if (shader.is_null() && shader_inc.is_null()) {
return;
}
shader_editor->toggle_inline_comment("//");
} break;
case EDIT_COMPLETE: {
shader_editor->get_text_editor()->request_code_completion();

View File

@ -1185,19 +1185,18 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2
Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
String text;
// Round floating point precision to 2 digits, as tiles don't have that much space.
switch (value.get_type()) {
case Variant::INT:
text = vformat("%d", value);
break;
case Variant::FLOAT:
text = vformat("%.2f", value);
break;
case Variant::STRING:
case Variant::STRING_NAME:
text = value;
case Variant::VECTOR2:
case Variant::VECTOR3:
case Variant::VECTOR4:
text = vformat("%.2v", value);
break;
default:
return;
text = value.stringify();
break;
}
@ -1216,8 +1215,8 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2
}
Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
p_canvas_item->draw_string_outline(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 1, Color(0, 0, 0, 1));
p_canvas_item->draw_string(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color);
p_canvas_item->draw_string_outline(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 4), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 3, Color(0, 0, 0));
p_canvas_item->draw_string(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 4), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color);
}
}

View File

@ -1693,7 +1693,7 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) {
}
}
void TileSetAtlasSourceEditor::_unhandled_key_input(const Ref<InputEvent> &p_event) {
void TileSetAtlasSourceEditor::shortcut_input(const Ref<InputEvent> &p_event) {
// Check for shortcuts.
if (ED_IS_SHORTCUT("tiles_editor/delete_tile", p_event)) {
if (tools_button_group->get_pressed_button() == tool_select_button && !selection.is_empty()) {
@ -2418,14 +2418,14 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {
}
void TileSetAtlasSourceEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileSetAtlasSourceEditor::_unhandled_key_input);
ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array);
ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
}
TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
set_process_unhandled_key_input(true);
set_shortcut_context(this);
set_process_shortcut_input(true);
set_process_internal(true);
// Middle panel.

View File

@ -257,9 +257,6 @@ private:
void _update_atlas_view();
void _update_toolbar();
// -- input events --
void _unhandled_key_input(const Ref<InputEvent> &p_event);
// -- Misc --
void _auto_create_tiles();
void _auto_remove_tiles();
@ -275,6 +272,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
// -- input events --
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id);
void init_source();

View File

@ -336,7 +336,7 @@ bool ProjectConverter3To4::convert() {
// Checking if folder contains valid Godot 3 project.
// Project should not be converted more than once.
{
String converter_text = "; Project was converted by built-in tool to Godot 4.0";
String converter_text = "; Project was converted by built-in tool to Godot 4";
ERR_FAIL_COND_V_MSG(!FileAccess::exists("project.godot"), false, "Current working directory doesn't contain a \"project.godot\" file for a Godot 3 project.");
@ -536,7 +536,7 @@ bool ProjectConverter3To4::validate_conversion() {
// Checking if folder contains valid Godot 3 project.
// Project should not be converted more than once.
{
String conventer_text = "; Project was converted by built-in tool to Godot 4.0";
String conventer_text = "; Project was converted by built-in tool to Godot 4";
ERR_FAIL_COND_V_MSG(!FileAccess::exists("project.godot"), false, "Current directory doesn't contain any Godot 3 project");
@ -857,16 +857,16 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
valid = valid && test_conversion_gdscript_builtin("\tif OS.get_borderless_window(): pass", "\tif get_window().borderless: pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tOS.set_borderless_window(Settings.borderless)", "\tget_window().borderless = (Settings.borderless)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tvar aa = roman(r.move_and_slide( a, b, c, d, e, f )) # Roman", "\tr.set_velocity(a)\n\tr.set_up_direction(b)\n\tr.set_floor_stop_on_slope_enabled(c)\n\tr.set_max_slides(d)\n\tr.set_floor_max_angle(e)\n\t# TODOConverter40 infinite_inertia were removed in Godot 4.0 - previous value `f`\n\tr.move_and_slide()\n\tvar aa = roman(r.velocity) # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tmove_and_slide( a, b, c, d, e, f ) # Roman", "\tset_velocity(a)\n\tset_up_direction(b)\n\tset_floor_stop_on_slope_enabled(c)\n\tset_max_slides(d)\n\tset_floor_max_angle(e)\n\t# TODOConverter40 infinite_inertia were removed in Godot 4.0 - previous value `f`\n\tmove_and_slide() # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tvar aa = roman(r.move_and_slide_with_snap( a, g, b, c, d, e, f )) # Roman", "\tr.set_velocity(a)\n\t# TODOConverter40 looks that snap in Godot 4.0 is float, not vector like in Godot 3 - previous value `g`\n\tr.set_up_direction(b)\n\tr.set_floor_stop_on_slope_enabled(c)\n\tr.set_max_slides(d)\n\tr.set_floor_max_angle(e)\n\t# TODOConverter40 infinite_inertia were removed in Godot 4.0 - previous value `f`\n\tr.move_and_slide()\n\tvar aa = roman(r.velocity) # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tmove_and_slide_with_snap( a, g, b, c, d, e, f ) # Roman", "\tset_velocity(a)\n\t# TODOConverter40 looks that snap in Godot 4.0 is float, not vector like in Godot 3 - previous value `g`\n\tset_up_direction(b)\n\tset_floor_stop_on_slope_enabled(c)\n\tset_max_slides(d)\n\tset_floor_max_angle(e)\n\t# TODOConverter40 infinite_inertia were removed in Godot 4.0 - previous value `f`\n\tmove_and_slide() # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tvar aa = roman(r.move_and_slide( a, b, c, d, e, f )) # Roman", "\tr.set_velocity(a)\n\tr.set_up_direction(b)\n\tr.set_floor_stop_on_slope_enabled(c)\n\tr.set_max_slides(d)\n\tr.set_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tr.move_and_slide()\n\tvar aa = roman(r.velocity) # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tmove_and_slide( a, b, c, d, e, f ) # Roman", "\tset_velocity(a)\n\tset_up_direction(b)\n\tset_floor_stop_on_slope_enabled(c)\n\tset_max_slides(d)\n\tset_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tmove_and_slide() # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tvar aa = roman(r.move_and_slide_with_snap( a, g, b, c, d, e, f )) # Roman", "\tr.set_velocity(a)\n\t# TODOConverter3To4 looks that snap in Godot 4 is float, not vector like in Godot 3 - previous value `g`\n\tr.set_up_direction(b)\n\tr.set_floor_stop_on_slope_enabled(c)\n\tr.set_max_slides(d)\n\tr.set_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tr.move_and_slide()\n\tvar aa = roman(r.velocity) # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tmove_and_slide_with_snap( a, g, b, c, d, e, f ) # Roman", "\tset_velocity(a)\n\t# TODOConverter3To4 looks that snap in Godot 4 is float, not vector like in Godot 3 - previous value `g`\n\tset_up_direction(b)\n\tset_floor_stop_on_slope_enabled(c)\n\tset_max_slides(d)\n\tset_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tmove_and_slide() # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("remove_and_slide(a,b,c,d,e,f)", "remove_and_slide(a,b,c,d,e,f)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a , b )", "list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a )", "list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("list_dir_begin( )", "list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a , b )", "list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a )", "list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("list_dir_begin( )", "list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("sort_custom( a , b )", "sort_custom(Callable(a, b))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@ -874,9 +874,9 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
valid = valid && test_conversion_gdscript_builtin("draw_line(1, 2, 3, 4, 5)", "draw_line(1, 2, 3, 4)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\timage.lock()", "\tfalse # image.lock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\timage.unlock()", "\tfalse # image.unlock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\troman.image.unlock()", "\tfalse # roman.image.unlock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\timage.lock()", "\tfalse # image.lock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\timage.unlock()", "\tfalse # image.unlock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\troman.image.unlock()", "\tfalse # roman.image.unlock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tmtx.lock()", "\tmtx.lock()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("\tmutex.unlock()", "\tmutex.unlock()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@ -998,7 +998,7 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
valid = valid && test_conversion_gdscript_builtin("apply_force(position, impulse)", "apply_force(impulse, position)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("apply_impulse(position, impulse)", "apply_impulse(impulse, position)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("draw_rect(a,b,c,d,e).abc", "draw_rect(a, b, c, d).abc# e) TODOGODOT4 Antialiasing argument is missing", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("draw_rect(a,b,c,d,e).abc", "draw_rect(a, b, c, d).abc# e) TODOConverter3To4 Antialiasing argument is missing", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("get_focus_owner()", "get_viewport().gui_get_focus_owner()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("button.pressed = 1", "button.button_pressed = 1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("button.pressed=1", "button.button_pressed=1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@ -1149,13 +1149,13 @@ bool ProjectConverter3To4::test_array_names() {
// Light2D, Texture, Viewport are special classes(probably virtual ones).
if (ClassDB::class_exists(StringName(old_class)) && old_class != "Light2D" && old_class != "Texture" && old_class != "Viewport") {
ERR_PRINT(vformat("Class \"%s\" exists in Godot 4.0, so it cannot be renamed to something else.", old_class));
ERR_PRINT(vformat("Class \"%s\" exists in Godot 4, so it cannot be renamed to something else.", old_class));
valid = false; // This probably should be only a warning, but not 100% sure - this would need to be added to CI.
}
// Callable is special class, to which normal classes may be renamed.
if (!ClassDB::class_exists(StringName(new_class)) && new_class != "Callable") {
ERR_PRINT(vformat("Class \"%s\" does not exist in Godot 4.0, so it cannot be used in the conversion.", old_class));
ERR_PRINT(vformat("Class \"%s\" does not exist in Godot 4, so it cannot be used in the conversion.", old_class));
valid = false; // This probably should be only a warning, but not 100% sure - this would need to be added to CI.
}
}
@ -1630,8 +1630,8 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
// With longer lines, doing so can sometimes be significantly faster.
if ((line.contains(".lock") || line.contains(".unlock")) && !line.contains("mtx") && !line.contains("mutex") && !line.contains("Mutex")) {
line = reg_container.reg_image_lock.sub(line, "false # $1.lock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", true);
line = reg_container.reg_image_unlock.sub(line, "false # $1.unlock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", true);
line = reg_container.reg_image_lock.sub(line, "false # $1.lock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", true);
line = reg_container.reg_image_unlock.sub(line, "false # $1.unlock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", true);
}
// PackedStringArray(req_godot).join('.') -> '.'.join(PackedStringArray(req_godot)) PoolStringArray
@ -1832,7 +1832,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
// infiinite_interia
if (parts.size() >= 6) {
line_new += starting_space + "# TODOConverter40 infinite_inertia were removed in Godot 4.0 - previous value `" + parts[5] + "`\n";
line_new += starting_space + "# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `" + parts[5] + "`\n";
}
line_new += starting_space + base_obj + "move_and_slide()";
@ -1863,7 +1863,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
// snap
if (parts.size() >= 2) {
line_new += starting_space + "# TODOConverter40 looks that snap in Godot 4.0 is float, not vector like in Godot 3 - previous value `" + parts[1] + "`\n";
line_new += starting_space + "# TODOConverter3To4 looks that snap in Godot 4 is float, not vector like in Godot 3 - previous value `" + parts[1] + "`\n";
}
// up_direction
@ -1888,7 +1888,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
// infiinite_interia
if (parts.size() >= 7) {
line_new += starting_space + "# TODOConverter40 infinite_inertia were removed in Godot 4.0 - previous value `" + parts[6] + "`\n";
line_new += starting_space + "# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `" + parts[6] + "`\n";
}
line_new += starting_space + base_obj + "move_and_slide()";
@ -1919,7 +1919,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
int start = line.find("list_dir_begin(");
int end = get_end_parenthesis(line.substr(start)) + 1;
if (end > -1) {
line = line.substr(0, start) + "list_dir_begin() " + line.substr(end + start) + "# TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547";
line = line.substr(0, start) + "list_dir_begin() " + line.substr(end + start) + "# TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547";
}
}
@ -2203,14 +2203,14 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
}
}
}
// draw_rect(a,b,c,d,e) -> draw_rect(a,b,c,d)#e) TODOGODOT4 Antialiasing argument is missing
// draw_rect(a,b,c,d,e) -> draw_rect(a,b,c,d)#e) TODOConverter3To4 Antialiasing argument is missing
if (contains_function_call(line, "draw_rect(")) {
int start = line.find("draw_rect(");
int end = get_end_parenthesis(line.substr(start)) + 1;
if (end > -1) {
Vector<String> parts = parse_arguments(line.substr(start, end));
if (parts.size() == 5) {
line = line.substr(0, start) + "draw_rect(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ", " + parts[3] + ")" + line.substr(end + start) + "# " + parts[4] + ") TODOGODOT4 Antialiasing argument is missing";
line = line.substr(0, start) + "draw_rect(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ", " + parts[3] + ")" + line.substr(end + start) + "# " + parts[4] + ") TODOConverter3To4 Antialiasing argument is missing";
}
}
}
@ -2241,9 +2241,10 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
// rotating = true -> ignore_rotation = false # reversed "rotating" for Camera2D
if (contains_function_call(line, "rotating")) {
int start = line.find("rotating");
String function_name = "rotating";
int start = line.find(function_name);
bool foundNextEqual = false;
String line_to_check = line.substr(start + String("rotating").length());
String line_to_check = line.substr(start + function_name.length());
String assigned_value;
for (int current_index = 0; line_to_check.length() > current_index; current_index++) {
char32_t chr = line_to_check.get(current_index);
@ -2251,14 +2252,14 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
continue;
} else if (chr == '=') {
foundNextEqual = true;
assigned_value = line.right(current_index).strip_edges();
assigned_value = line.substr(start + function_name.length() + current_index + 1).strip_edges();
assigned_value = assigned_value == "true" ? "false" : "true";
} else {
break;
}
}
if (foundNextEqual) {
line = line.substr(0, start) + "ignore_rotation =" + assigned_value + " # reversed \"rotating\" for Camera2D";
line = line.substr(0, start) + "ignore_rotation = " + assigned_value + " # reversed \"rotating\" for Camera2D";
}
}

View File

@ -1042,10 +1042,25 @@ void ProjectListItemControl::set_project_icon(const Ref<Texture2D> &p_icon) {
project_icon->set_texture(p_icon);
}
void ProjectListItemControl::set_unsupported_features(const PackedStringArray &p_features) {
bool _project_feature_looks_like_version(const String &p_feature) {
return p_feature.contains(".") && p_feature.substr(0, 3).is_numeric();
}
void ProjectListItemControl::set_unsupported_features(PackedStringArray p_features) {
if (p_features.size() > 0) {
String unsupported_features_str = String(", ").join(p_features);
project_unsupported_features->set_tooltip_text(TTR("The project uses features unsupported by the current build:") + "\n" + unsupported_features_str);
String tooltip_text = "";
for (int i = 0; i < p_features.size(); i++) {
if (_project_feature_looks_like_version(p_features[i])) {
tooltip_text += TTR("This project was last edited in a different Godot version: ") + p_features[i] + "\n";
p_features.remove_at(i);
i--;
}
}
if (p_features.size() > 0) {
String unsupported_features_str = String(", ").join(p_features);
tooltip_text += TTR("This project uses features unsupported by the current build:") + "\n" + unsupported_features_str;
}
project_unsupported_features->set_tooltip_text(tooltip_text);
project_unsupported_features->show();
} else {
project_unsupported_features->hide();
@ -1450,7 +1465,7 @@ void ProjectList::_create_project_item_control(int p_index) {
hb->set_project_path(item.path);
hb->set_tooltip_text(item.description);
hb->set_tags(item.tags, this);
hb->set_unsupported_features(item.unsupported_features);
hb->set_unsupported_features(item.unsupported_features.duplicate());
hb->set_is_favorite(item.favorite);
hb->set_is_missing(item.missing);
@ -2297,8 +2312,8 @@ void ProjectManager::_open_selected_projects_ask() {
warning_message += TTR("Warning: This project uses C#, but this build of Godot does not have\nthe Mono module. If you proceed you will not be able to use any C# scripts.\n\n");
unsupported_features.remove_at(i);
i--;
} else if (feature.substr(0, 3).is_numeric()) {
warning_message += vformat(TTR("Warning: This project was built in Godot %s.\nOpening will upgrade or downgrade the project to Godot %s.\n\n"), Variant(feature), Variant(VERSION_BRANCH));
} else if (_project_feature_looks_like_version(feature)) {
warning_message += vformat(TTR("Warning: This project was last edited in Godot %s. Opening will change it to Godot %s.\n\n"), Variant(feature), Variant(VERSION_BRANCH));
unsupported_features.remove_at(i);
i--;
}
@ -2337,6 +2352,8 @@ void ProjectManager::_perform_full_project_conversion() {
args.push_back("--path");
args.push_back(path);
args.push_back("--convert-3to4");
args.push_back("--rendering-driver");
args.push_back(Main::get_rendering_driver_name());
Error err = OS::get_singleton()->create_instance(args);
ERR_FAIL_COND(err);
@ -2504,6 +2521,7 @@ void ProjectManager::_apply_project_tags() {
callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred(); // Make sure the dialog does not disappear.
return;
} else {
tags.sort();
cfg.set_value("application", "config/tags", tags);
err = cfg.save(project_godot);
if (err != OK) {
@ -3095,7 +3113,7 @@ ProjectManager::ProjectManager() {
ask_full_convert_dialog = memnew(ConfirmationDialog);
ask_full_convert_dialog->set_autowrap(true);
ask_full_convert_dialog->set_text(TTR("This option will perform full project conversion, updating scenes, resources and scripts from Godot 3.x to work in Godot 4.0.\n\nNote that this is a best-effort conversion, i.e. it makes upgrading the project easier, but it will not open out-of-the-box and will still require manual adjustments.\n\nIMPORTANT: Make sure to backup your project before converting, as this operation makes it impossible to open it in older versions of Godot."));
ask_full_convert_dialog->set_text(TTR("This option will perform full project conversion, updating scenes, resources and scripts from Godot 3 to work in Godot 4.\n\nNote that this is a best-effort conversion, i.e. it makes upgrading the project easier, but it will not open out-of-the-box and will still require manual adjustments.\n\nIMPORTANT: Make sure to backup your project before converting, as this operation makes it impossible to open it in older versions of Godot."));
ask_full_convert_dialog->connect("confirmed", callable_mp(this, &ProjectManager::_perform_full_project_conversion));
add_child(ask_full_convert_dialog);

View File

@ -166,7 +166,7 @@ public:
void set_project_path(const String &p_path);
void set_tags(const PackedStringArray &p_tags, ProjectList *p_parent_list);
void set_project_icon(const Ref<Texture2D> &p_icon);
void set_unsupported_features(const PackedStringArray &p_features);
void set_unsupported_features(PackedStringArray p_features);
bool should_load_project_icon() const;
void set_selected(bool p_selected);

View File

@ -464,7 +464,7 @@ String ShaderCreateDialog::_validate_path(const String &p_path) {
}
void ShaderCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {
error_label->set_text("- " + p_msg);
error_label->set_text(String::utf8("") + p_msg);
if (valid) {
error_label->add_theme_color_override("font_color", gc->get_theme_color(SNAME("success_color"), SNAME("Editor")));
} else {
@ -473,7 +473,7 @@ void ShaderCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {
}
void ShaderCreateDialog::_msg_path_valid(bool valid, const String &p_msg) {
path_error_label->set_text("- " + p_msg);
path_error_label->set_text(String::utf8("") + p_msg);
if (valid) {
path_error_label->add_theme_color_override("font_color", gc->get_theme_color(SNAME("success_color"), SNAME("Editor")));
} else {

View File

@ -1956,6 +1956,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->_verbose_stdout = GLOBAL_GET("debug/settings/stdout/verbose_stdout");
}
#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
OS::get_singleton()->set_environment("MVK_CONFIG_LOG_LEVEL", OS::get_singleton()->_verbose_stdout ? "3" : "1"); // 1 = Errors only, 3 = Info
#endif
if (frame_delay == 0) {
frame_delay = GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/frame_delay_msec", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), 0);
}

View File

@ -236,6 +236,8 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[name] = m_enum->start_line;
doc.enums[name] = m_enum->doc_description;
for (const GDP::EnumNode::Value &val : m_enum->values) {
DocData::ConstantDoc const_doc;
const_doc.name = val.identifier->name;
@ -244,7 +246,6 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
const_doc.description = val.doc_description;
const_doc.enumeration = name;
doc.enums[const_doc.name] = const_doc.description;
doc.constants.push_back(const_doc);
}

View File

@ -3229,7 +3229,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *
}
GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
push_error(R"("yield" was removed in Godot 4. Use "await" instead.)");
return nullptr;
}

View File

@ -1,2 +1,2 @@
GDTEST_PARSER_ERROR
"yield" was removed in Godot 4.0. Use "await" instead.
"yield" was removed in Godot 4. Use "await" instead.

View File

@ -2272,7 +2272,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs) {
if (efs && !p_script->get_path().is_empty()) {
efs->update_file(p_script->get_path());
}
#endif

View File

@ -37,7 +37,7 @@ namespace Godot.SourceGenerators
while (symbol != null)
{
if (symbol.ContainingAssembly?.Name == assemblyName &&
symbol.ToString() == typeFullName)
symbol.FullQualifiedNameOmitGlobal() == typeFullName)
{
return true;
}
@ -230,22 +230,22 @@ namespace Godot.SourceGenerators
.Replace(">", ")");
public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.ExportAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportAttr;
public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SignalAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SignalAttr;
public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.MustBeVariantAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.MustBeVariantAttr;
public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GodotClassNameAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GodotClassNameAttr;
public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GlobalClassAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr;
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SystemFlagsAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr;
public static GodotMethodData? HasGodotCompatibleSignature(
this IMethodSymbol method,

View File

@ -362,7 +362,7 @@ namespace Godot.SourceGenerators
{
foreach (var attr in memberSymbol.GetAttributes())
{
PropertyUsageFlags? propertyUsage = attr.AttributeClass?.ToString() switch
PropertyUsageFlags? propertyUsage = attr.AttributeClass?.FullQualifiedNameOmitGlobal() switch
{
GodotClasses.ExportCategoryAttr => PropertyUsageFlags.Category,
GodotClasses.ExportGroupAttr => PropertyUsageFlags.Group,

View File

@ -28,10 +28,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.1" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<!-- For RiderPathLocator -->
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<Reference Include="GodotSharp">
<HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>
<Private>False</Private>

View File

@ -0,0 +1,51 @@
using System;
using Godot;
using JetBrains.Rider.PathLocator;
using Newtonsoft.Json;
using OS = GodotTools.Utils.OS;
namespace GodotTools.Ides.Rider;
public class RiderLocatorEnvironment : IRiderLocatorEnvironment
{
public JetBrains.Rider.PathLocator.OS CurrentOS
{
get
{
if (OS.IsWindows)
return JetBrains.Rider.PathLocator.OS.Windows;
if (OS.IsMacOS) return JetBrains.Rider.PathLocator.OS.MacOSX;
if (OS.IsUnixLike) return JetBrains.Rider.PathLocator.OS.Linux;
return JetBrains.Rider.PathLocator.OS.Other;
}
}
public T FromJson<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json);
}
public void Info(string message, Exception e = null)
{
if (e == null)
GD.Print(message);
else
GD.Print(message, e);
}
public void Warn(string message, Exception e = null)
{
if (e == null)
GD.PushWarning(message);
else
GD.PushWarning(message, e);
}
public void Error(string message, Exception e = null)
{
if (e == null)
GD.PushError(message);
else
GD.PushError(message, e);
}
}

View File

@ -1,474 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using Godot;
using Microsoft.Win32;
using Newtonsoft.Json;
using Directory = System.IO.Directory;
using Environment = System.Environment;
using File = System.IO.File;
using Path = System.IO.Path;
using OS = GodotTools.Utils.OS;
// ReSharper disable UnassignedField.Local
// ReSharper disable InconsistentNaming
// ReSharper disable UnassignedField.Global
// ReSharper disable MemberHidesStaticFromOuterClass
namespace GodotTools.Ides.Rider
{
/// <summary>
/// This code is a modified version of the JetBrains resharper-unity plugin listed under Apache License 2.0 license:
/// https://github.com/JetBrains/resharper-unity/blob/master/unity/JetBrains.Rider.Unity.Editor/EditorPlugin/RiderPathLocator.cs
/// </summary>
public static class RiderPathLocator
{
public static RiderInfo[] GetAllRiderPaths()
{
try
{
if (OS.IsWindows)
{
return CollectRiderInfosWindows();
}
if (OS.IsMacOS)
{
return CollectRiderInfosMac();
}
if (OS.IsUnixLike)
{
return CollectAllRiderPathsLinux();
}
throw new InvalidOperationException("Unexpected OS.");
}
catch (Exception e)
{
GD.PushWarning(e.Message);
}
return Array.Empty<RiderInfo>();
}
private static RiderInfo[] CollectAllRiderPathsLinux()
{
var installInfos = new List<RiderInfo>();
string home = Environment.GetEnvironmentVariable("HOME");
if (!string.IsNullOrEmpty(home))
{
string toolboxRiderRootPath = GetToolboxBaseDir();
installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false)
.Select(a => new RiderInfo(a, true)).ToList());
//$Home/.local/share/applications/jetbrains-rider.desktop
var shortcut = new FileInfo(Path.Combine(home, @".local/share/applications/jetbrains-rider.desktop"));
if (shortcut.Exists)
{
string[] lines = File.ReadAllLines(shortcut.FullName);
foreach (string line in lines)
{
if (!line.StartsWith("Exec=\""))
continue;
string path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault();
if (string.IsNullOrEmpty(path))
continue;
if (installInfos.Any(a => a.Path == path)) // avoid adding similar build as from toolbox
continue;
installInfos.Add(new RiderInfo(path, false));
}
}
}
// snap install
string snapInstallPath = "/snap/rider/current/bin/rider.sh";
if (new FileInfo(snapInstallPath).Exists)
installInfos.Add(new RiderInfo(snapInstallPath, false));
return installInfos.ToArray();
}
private static RiderInfo[] CollectRiderInfosMac()
{
var installInfos = new List<RiderInfo>();
// "/Applications/*Rider*.app"
// should be combined with "Contents/MacOS/rider"
var folder = new DirectoryInfo("/Applications");
if (folder.Exists)
{
installInfos.AddRange(folder.GetDirectories("*Rider*.app")
.Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false))
.ToList());
}
// /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app
// should be combined with "Contents/MacOS/rider"
string toolboxRiderRootPath = GetToolboxBaseDir();
var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true)
.Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true));
installInfos.AddRange(paths);
return installInfos.ToArray();
}
[SupportedOSPlatform("windows")]
private static RiderInfo[] CollectRiderInfosWindows()
{
var installInfos = new List<RiderInfo>();
var toolboxRiderRootPath = GetToolboxBaseDir();
var installPathsToolbox = CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider64.exe", false).ToList();
installInfos.AddRange(installPathsToolbox.Select(a => new RiderInfo(a, true)).ToList());
var installPaths = new List<string>();
const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
CollectPathsFromRegistry(registryKey, installPaths);
const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
CollectPathsFromRegistry(wowRegistryKey, installPaths);
installInfos.AddRange(installPaths.Select(a => new RiderInfo(a, false)).ToList());
return installInfos.ToArray();
}
private static string GetToolboxBaseDir()
{
if (OS.IsWindows)
{
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
return GetToolboxRiderRootPath(localAppData);
}
if (OS.IsMacOS)
{
var home = Environment.GetEnvironmentVariable("HOME");
if (string.IsNullOrEmpty(home))
return string.Empty;
var localAppData = Path.Combine(home, @"Library/Application Support");
return GetToolboxRiderRootPath(localAppData);
}
if (OS.IsUnixLike)
{
var home = Environment.GetEnvironmentVariable("HOME");
if (string.IsNullOrEmpty(home))
return string.Empty;
var localAppData = Path.Combine(home, @".local/share");
return GetToolboxRiderRootPath(localAppData);
}
return string.Empty;
}
private static string GetToolboxRiderRootPath(string localAppData)
{
var toolboxPath = Path.Combine(localAppData, @"JetBrains/Toolbox");
var settingsJson = Path.Combine(toolboxPath, ".settings.json");
if (File.Exists(settingsJson))
{
var path = SettingsJson.GetInstallLocationFromJson(File.ReadAllText(settingsJson));
if (!string.IsNullOrEmpty(path))
toolboxPath = path;
}
var toolboxRiderRootPath = Path.Combine(toolboxPath, @"apps/Rider");
return toolboxRiderRootPath;
}
internal static ProductInfo GetBuildVersion(string path)
{
var buildTxtFileInfo = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt()));
var dir = buildTxtFileInfo.DirectoryName;
if (!Directory.Exists(dir))
return null;
var buildVersionFile = new FileInfo(Path.Combine(dir, "product-info.json"));
if (!buildVersionFile.Exists)
return null;
var json = File.ReadAllText(buildVersionFile.FullName);
return ProductInfo.GetProductInfo(json);
}
internal static Version GetBuildNumber(string path)
{
var file = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt()));
if (!file.Exists)
return null;
var text = File.ReadAllText(file.FullName);
if (text.Length <= 3)
return null;
var versionText = text.Substring(3);
return Version.TryParse(versionText, out var v) ? v : null;
}
internal static bool IsToolbox(string path)
{
return path.StartsWith(GetToolboxBaseDir());
}
private static string GetRelativePathToBuildTxt()
{
if (OS.IsWindows || OS.IsUnixLike)
return "../../build.txt";
if (OS.IsMacOS)
return "Contents/Resources/build.txt";
throw new InvalidOperationException("Unknown OS.");
}
[SupportedOSPlatform("windows")]
private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths)
{
using (var key = Registry.CurrentUser.OpenSubKey(registryKey))
{
CollectPathsFromRegistry(installPaths, key);
}
using (var key = Registry.LocalMachine.OpenSubKey(registryKey))
{
CollectPathsFromRegistry(installPaths, key);
}
}
[SupportedOSPlatform("windows")]
private static void CollectPathsFromRegistry(List<string> installPaths, RegistryKey key)
{
if (key == null) return;
foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider")))
{
using (var subkey = key.OpenSubKey(subkeyName))
{
var folderObject = subkey?.GetValue("InstallLocation");
if (folderObject == null) continue;
var folder = folderObject.ToString();
var possiblePath = Path.Combine(folder, @"bin\rider64.exe");
if (File.Exists(possiblePath))
installPaths.Add(possiblePath);
}
}
}
private static string[] CollectPathsFromToolbox(string toolboxRiderRootPath, string dirName, string searchPattern,
bool isMac)
{
if (!Directory.Exists(toolboxRiderRootPath))
return Array.Empty<string>();
var channelDirs = Directory.GetDirectories(toolboxRiderRootPath);
var paths = channelDirs.SelectMany(channelDir =>
{
try
{
// use history.json - last entry stands for the active build https://jetbrains.slack.com/archives/C07KNP99D/p1547807024066500?thread_ts=1547731708.057700&cid=C07KNP99D
var historyFile = Path.Combine(channelDir, ".history.json");
if (File.Exists(historyFile))
{
var json = File.ReadAllText(historyFile);
var build = ToolboxHistory.GetLatestBuildFromJson(json);
if (build != null)
{
var buildDir = Path.Combine(channelDir, build);
var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir);
if (executablePaths.Any())
return executablePaths;
}
}
var channelFile = Path.Combine(channelDir, ".channel.settings.json");
if (File.Exists(channelFile))
{
var json = File.ReadAllText(channelFile).Replace("active-application", "active_application");
var build = ToolboxInstallData.GetLatestBuildFromJson(json);
if (build != null)
{
var buildDir = Path.Combine(channelDir, build);
var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir);
if (executablePaths.Any())
return executablePaths;
}
}
// changes in toolbox json files format may brake the logic above, so return all found Rider installations
return Directory.GetDirectories(channelDir)
.SelectMany(buildDir => GetExecutablePaths(dirName, searchPattern, isMac, buildDir));
}
catch (Exception e)
{
// do not write to Debug.Log, just log it.
Logger.Warn($"Failed to get RiderPath from {channelDir}", e);
}
return Array.Empty<string>();
})
.Where(c => !string.IsNullOrEmpty(c))
.ToArray();
return paths;
}
private static string[] GetExecutablePaths(string dirName, string searchPattern, bool isMac, string buildDir)
{
var folder = new DirectoryInfo(Path.Combine(buildDir, dirName));
if (!folder.Exists)
return Array.Empty<string>();
if (!isMac)
return new[] { Path.Combine(folder.FullName, searchPattern) }.Where(File.Exists).ToArray();
return folder.GetDirectories(searchPattern).Select(f => f.FullName)
.Where(Directory.Exists).ToArray();
}
// Disable the "field is never assigned" compiler warning. We never assign it, but Unity does.
// Note that Unity disable this warning in the generated C# projects
#pragma warning disable 0649
[Serializable]
class SettingsJson
{
public string install_location;
[return: MaybeNull]
public static string GetInstallLocationFromJson(string json)
{
try
{
return JsonConvert.DeserializeObject<SettingsJson>(json).install_location;
}
catch (Exception)
{
Logger.Warn($"Failed to get install_location from json {json}");
}
return null;
}
}
[Serializable]
class ToolboxHistory
{
public List<ItemNode> history;
public static string GetLatestBuildFromJson(string json)
{
try
{
return JsonConvert.DeserializeObject<ToolboxHistory>(json).history.LastOrDefault()?.item.build;
}
catch (Exception)
{
Logger.Warn($"Failed to get latest build from json {json}");
}
return null;
}
}
[Serializable]
class ItemNode
{
public BuildNode item;
}
[Serializable]
class BuildNode
{
public string build;
}
[Serializable]
public class ProductInfo
{
public string version;
public string versionSuffix;
[return: MaybeNull]
internal static ProductInfo GetProductInfo(string json)
{
try
{
var productInfo = JsonConvert.DeserializeObject<ProductInfo>(json);
return productInfo;
}
catch (Exception)
{
Logger.Warn($"Failed to get version from json {json}");
}
return null;
}
}
// ReSharper disable once ClassNeverInstantiated.Global
[Serializable]
class ToolboxInstallData
{
// ReSharper disable once InconsistentNaming
public ActiveApplication active_application;
[return: MaybeNull]
public static string GetLatestBuildFromJson(string json)
{
try
{
var toolbox = JsonConvert.DeserializeObject<ToolboxInstallData>(json);
var builds = toolbox.active_application.builds;
if (builds != null && builds.Any())
return builds.First();
}
catch (Exception)
{
Logger.Warn($"Failed to get latest build from json {json}");
}
return null;
}
}
[Serializable]
class ActiveApplication
{
public List<string> builds;
}
#pragma warning restore 0649
public struct RiderInfo
{
// ReSharper disable once NotAccessedField.Global
public bool IsToolbox;
public string Presentation;
public Version BuildNumber;
public ProductInfo ProductInfo;
public string Path;
public RiderInfo(string path, bool isToolbox)
{
BuildNumber = GetBuildNumber(path);
ProductInfo = GetBuildVersion(path);
Path = new FileInfo(path).FullName; // normalize separators
var presentation = $"Rider {BuildNumber}";
if (ProductInfo != null && !string.IsNullOrEmpty(ProductInfo.version))
{
var suffix = string.IsNullOrEmpty(ProductInfo.versionSuffix) ? "" : $" {ProductInfo.versionSuffix}";
presentation = $"Rider {ProductInfo.version}{suffix}";
}
if (isToolbox)
presentation += " (JetBrains Toolbox)";
Presentation = presentation;
IsToolbox = isToolbox;
}
}
private static class Logger
{
internal static void Warn(string message, Exception e = null)
{
throw new Exception(message, e);
}
}
}
}

View File

@ -4,11 +4,19 @@ using System.IO;
using System.Linq;
using Godot;
using GodotTools.Internals;
using JetBrains.Rider.PathLocator;
namespace GodotTools.Ides.Rider
{
public static class RiderPathManager
{
private static readonly RiderPathLocator RiderPathLocator;
static RiderPathManager()
{
RiderPathLocator = new RiderPathLocator(new RiderLocatorEnvironment());
}
public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional";
private static string GetRiderPathFromSettings()

View File

@ -38,7 +38,7 @@ internal static class ExtensionMethods
}
private static bool IsGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GeneratorClasses.GenerateUnmanagedCallbacksAttr;
=> symbol.FullQualifiedNameOmitGlobal() == GeneratorClasses.GenerateUnmanagedCallbacksAttr;
public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectUnmanagedCallbacksClasses(
this IEnumerable<ClassDeclarationSyntax> source,

View File

@ -125,7 +125,10 @@ namespace Godot
NativePtr = IntPtr.Zero;
}
DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf);
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf);
}
}
/// <summary>

View File

@ -301,6 +301,46 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
}
}
// Search all faces of start polygon as well.
bool closest_point_on_start_poly = false;
for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
Vector3 spoint = f.get_closest_point_to(p_destination);
real_t dpoint = spoint.distance_to(p_destination);
if (dpoint < end_d) {
end_point = spoint;
end_d = dpoint;
closest_point_on_start_poly = true;
}
}
if (closest_point_on_start_poly) {
// No point to run PostProcessing when start and end convex polygon is the same.
if (r_path_types) {
r_path_types->resize(2);
r_path_types->write[0] = begin_poly->owner->get_type();
r_path_types->write[1] = begin_poly->owner->get_type();
}
if (r_path_rids) {
r_path_rids->resize(2);
(*r_path_rids)[0] = begin_poly->owner->get_self();
(*r_path_rids)[1] = begin_poly->owner->get_self();
}
if (r_path_owners) {
r_path_owners->resize(2);
r_path_owners->write[0] = begin_poly->owner->get_owner_id();
r_path_owners->write[1] = begin_poly->owner->get_owner_id();
}
Vector<Vector3> path;
path.resize(2);
path.write[0] = begin_point;
path.write[1] = end_point;
return path;
}
// Reset open and navigation_polys
gd::NavigationPoly np = navigation_polys[0];
navigation_polys.clear();
@ -346,9 +386,44 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
}
}
// If we did not find a route, return an empty path.
// We did not find a route but we have both a start polygon and an end polygon at this point.
// Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon.
if (!found_route) {
return Vector<Vector3>();
end_d = FLT_MAX;
// Search all faces of the start polygon for the closest point to our target position.
for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
Vector3 spoint = f.get_closest_point_to(p_destination);
real_t dpoint = spoint.distance_to(p_destination);
if (dpoint < end_d) {
end_point = spoint;
end_d = dpoint;
}
}
if (r_path_types) {
r_path_types->resize(2);
r_path_types->write[0] = begin_poly->owner->get_type();
r_path_types->write[1] = begin_poly->owner->get_type();
}
if (r_path_rids) {
r_path_rids->resize(2);
(*r_path_rids)[0] = begin_poly->owner->get_self();
(*r_path_rids)[1] = begin_poly->owner->get_self();
}
if (r_path_owners) {
r_path_owners->resize(2);
r_path_owners->write[0] = begin_poly->owner->get_owner_id();
r_path_owners->write[1] = begin_poly->owner->get_owner_id();
}
Vector<Vector3> path;
path.resize(2);
path.write[0] = begin_point;
path.write[1] = end_point;
return path;
}
Vector<Vector3> path;

View File

@ -167,7 +167,7 @@ Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_han
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
*r_resolved_path = path;

View File

@ -257,7 +257,7 @@ Error OS_IOS::open_dynamic_library(const String p_path, void *&p_library_handle,
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
*r_resolved_path = path;

View File

@ -235,11 +235,15 @@ def configure(env: "Environment"):
env.ParseConfig("pkg-config libenet --cflags --libs")
if not env["builtin_squish"]:
env.ParseConfig("pkg-config libsquish --cflags --libs")
# libsquish doesn't reliably install its .pc file, so some distros lack it.
env.Append(LIBS=["libsquish"])
if not env["builtin_zstd"]:
env.ParseConfig("pkg-config libzstd --cflags --libs")
if env["brotli"] and not env["builtin_brotli"]:
env.ParseConfig("pkg-config libbrotlicommon libbrotlidec --cflags --libs")
# Sound and video libraries
# Keep the order as it triggers chained dependencies (ogg needed by others, etc.)

View File

@ -98,19 +98,20 @@ static bool detect_sandbox() {
JoypadLinux::JoypadLinux(Input *in) {
#ifdef UDEV_ENABLED
#ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED
int dylibloader_verbose = 1;
#else
int dylibloader_verbose = 0;
#endif
if (detect_sandbox()) {
// Linux binaries in sandboxes / containers need special handling because
// libudev doesn't work there. So we need to fallback to manual parsing
// of /dev/input in such case.
use_udev = false;
print_verbose("JoypadLinux: udev enabled, but detected incompatible sandboxed mode. Falling back to /dev/input to detect joypads.");
} else {
}
#ifdef SOWRAP_ENABLED
else {
#ifdef DEBUG_ENABLED
int dylibloader_verbose = 1;
#else
int dylibloader_verbose = 0;
#endif
use_udev = initialize_libudev(dylibloader_verbose) == 0;
if (use_udev) {
if (!udev_new || !udev_unref || !udev_enumerate_new || !udev_enumerate_add_match_subsystem || !udev_enumerate_scan_devices || !udev_enumerate_get_list_entry || !udev_list_entry_get_next || !udev_list_entry_get_name || !udev_device_new_from_syspath || !udev_device_get_devnode || !udev_device_get_action || !udev_device_unref || !udev_enumerate_unref || !udev_monitor_new_from_netlink || !udev_monitor_filter_add_match_subsystem_devtype || !udev_monitor_enable_receiving || !udev_monitor_get_fd || !udev_monitor_receive_device || !udev_monitor_unref) {
@ -124,10 +125,11 @@ JoypadLinux::JoypadLinux(Input *in) {
print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads.");
}
}
#endif
#endif // SOWRAP_ENABLED
#else
print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads.");
#endif
#endif // UDEV_ENABLED
input = in;
monitor_joypads_thread.start(monitor_joypads_thread_func, this);
joypad_events_thread.start(joypad_events_thread_func, this);

View File

@ -954,45 +954,33 @@ static String get_mountpoint(const String &p_path) {
}
Error OS_LinuxBSD::move_to_trash(const String &p_path) {
String path = p_path.rstrip("/"); // Strip trailing slash when path points to a directory
// We try multiple methods, until we find one that works.
// So we only return on success until we exhausted possibilities.
String path = p_path.rstrip("/"); // Strip trailing slash when path points to a directory.
int err_code;
List<String> args;
args.push_back(path);
args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args.
args.push_front("trash"); // The command is `gio trash <file_name>` so we add it before the path.
Error result = execute("gio", args, nullptr, &err_code); // For GNOME based machines.
if (result == OK) { // The `execute` function has done its job without errors.
if (!err_code) { // The shell command has been executed without errors.
return OK;
} else if (err_code == 1) {
ERR_PRINT("move_to_trash: No such file or directory as " + path + ".");
return ERR_FILE_NOT_FOUND;
}
if (result == OK && err_code == 0) { // Success.
return OK;
}
args.pop_front();
args.push_front("move");
args.push_back("trash:/"); // The command is `kioclient5 move <file_name> trash:/`.
result = execute("kioclient5", args, nullptr, &err_code); // For KDE based machines.
if (result == OK) { // The `execute` function has done its job without errors.
if (!err_code) { // The shell command has been executed without errors.
return OK;
} else if (err_code == 1) {
ERR_PRINT("move_to_trash: No such file or directory as " + path + ".");
return ERR_FILE_NOT_FOUND;
}
if (result == OK && err_code == 0) {
return OK;
}
args.pop_front();
args.pop_back();
result = execute("gvfs-trash", args, nullptr, &err_code); // For older Linux machines.
if (result == OK) { // The `execute` function has done its job without errors.
if (!err_code) { // The shell command has been executed without errors.
return OK;
} else if (err_code == 1) {
ERR_PRINT("move_to_trash: No such file or directory as " + path + ".");
return ERR_FILE_NOT_FOUND;
}
if (result == OK && err_code == 0) {
return OK;
}
// If the commands `kioclient5`, `gio` or `gvfs-trash` don't work on the system we do it manually.

View File

@ -5449,7 +5449,9 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
#else
#ifdef XKB_ENABLED
xkb_loaded = true;
bool xkb_loaded = true;
xkb_loaded_v05p = true;
xkb_loaded_v08p = true;
#endif
#endif
@ -5476,6 +5478,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
r_error = OK;
#ifdef SOWRAP_ENABLED
{
if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) {
// There's no API to check version, check if functions are available instead.
@ -5484,6 +5487,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
return;
}
}
#endif
for (int i = 0; i < CURSOR_MAX; i++) {
cursors[i] = None;

View File

@ -1211,7 +1211,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
// Read Info.plist.
if (!p_info.is_empty()) {
print_verbose(vformat("CodeSign: Reading bundle info..."));
print_verbose("CodeSign: Reading bundle info...");
PList info_plist;
if (info_plist.load_file(p_info)) {
info_hash1 = file_hash_sha1(p_info);
@ -1262,7 +1262,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
}
}
} else if (MachO::is_macho(main_exe)) {
print_verbose(vformat("CodeSign: Executable is thin..."));
print_verbose("CodeSign: Executable is thin...");
files_to_sign.push_back(main_exe);
} else {
r_error_msg = TTR("Invalid binary format.");
@ -1284,7 +1284,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
// Generate core resources.
if (!p_bundle_path.is_empty()) {
print_verbose(vformat("CodeSign: Generating bundle CodeResources..."));
print_verbose("CodeSign: Generating bundle CodeResources...");
CodeSignCodeResources cr;
if (p_ios_bundle) {
@ -1366,7 +1366,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
CharString uuid_str = id.utf8();
print_verbose(vformat("CodeSign: Used bundle ID: %s", id));
print_verbose(vformat("CodeSign: Processing entitlements..."));
print_verbose("CodeSign: Processing entitlements...");
Ref<CodeSignEntitlementsText> cet;
Ref<CodeSignEntitlementsBinary> ceb;
@ -1381,7 +1381,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
ceb = Ref<CodeSignEntitlementsBinary>(memnew(CodeSignEntitlementsBinary(entitlements)));
}
print_verbose(vformat("CodeSign: Generating requirements..."));
print_verbose("CodeSign: Generating requirements...");
Ref<CodeSignRequirements> rq;
String team_id = "";
rq = Ref<CodeSignRequirements>(memnew(CodeSignRequirements()));
@ -1396,10 +1396,10 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
}
print_verbose(vformat("CodeSign: Signing executable for cputype: %d ...", mh.get_cputype()));
print_verbose(vformat("CodeSign: Generating CodeDirectory..."));
print_verbose("CodeSign: Generating CodeDirectory...");
Ref<CodeSignCodeDirectory> cd1 = memnew(CodeSignCodeDirectory(0x14, 0x01, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit()));
Ref<CodeSignCodeDirectory> cd2 = memnew(CodeSignCodeDirectory(0x20, 0x02, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit()));
print_verbose(vformat("CodeSign: Calculating special slot hashes..."));
print_verbose("CodeSign: Calculating special slot hashes...");
if (info_hash2.size() == 0x20) {
cd2->set_hash_in_slot(info_hash2, CodeSignCodeDirectory::SLOT_INFO_PLIST);
}
@ -1444,7 +1444,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
ERR_FAIL_V_MSG(FAILED, "CodeSign: Can't resize signature load command.");
}
print_verbose(vformat("CodeSign: Calculating executable code hashes..."));
print_verbose("CodeSign: Calculating executable code hashes...");
// Calculate executable code hashes.
PackedByteArray buffer;
PackedByteArray hash1, hash2;
@ -1481,11 +1481,11 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
cd1->set_hash_in_slot(hash1, cd1->get_page_count());
}
print_verbose(vformat("CodeSign: Generating signature..."));
print_verbose("CodeSign: Generating signature...");
Ref<CodeSignSignature> cs;
cs = Ref<CodeSignSignature>(memnew(CodeSignSignature()));
print_verbose(vformat("CodeSign: Writing signature superblob..."));
print_verbose("CodeSign: Writing signature superblob...");
// Write signature data to the executable.
CodeSignSuperBlob sb = CodeSignSuperBlob();
sb.add_blob(cd2);
@ -1502,7 +1502,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
sb.write_to_file(mh.get_file());
}
if (files_to_sign.size() > 1) {
print_verbose(vformat("CodeSign: Rebuilding fat executable..."));
print_verbose("CodeSign: Rebuilding fat executable...");
LipO lip;
if (!lip.create_file(main_exe, files_to_sign)) {
CLEANUP();

View File

@ -189,7 +189,7 @@ Error OS_MacOS::open_dynamic_library(const String p_path, void *&p_library_handl
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
*r_resolved_path = path;

View File

@ -131,9 +131,9 @@ void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options)
bool EditorExportPlatformUWP::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
#ifndef DEV_ENABLED
// We don't provide export templates for the UWP platform currently as it
// has not been ported for Godot 4.0. This is skipped in DEV_ENABLED so that
// has not been ported for Godot 4. This is skipped in DEV_ENABLED so that
// contributors can still test the pipeline if/when we can build it again.
r_error = "The UWP platform is currently not supported in Godot 4.0.\n";
r_error = "The UWP platform is currently not supported in Godot 4.\n";
return false;
#endif
@ -180,9 +180,9 @@ bool EditorExportPlatformUWP::has_valid_export_configuration(const Ref<EditorExp
bool EditorExportPlatformUWP::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
#ifndef DEV_ENABLED
// We don't provide export templates for the UWP platform currently as it
// has not been ported for Godot 4.0. This is skipped in DEV_ENABLED so that
// has not been ported for Godot 4. This is skipped in DEV_ENABLED so that
// contributors can still test the pipeline if/when we can build it again.
r_error = "The UWP platform is currently not supported in Godot 4.0.\n";
r_error = "The UWP platform is currently not supported in Godot 4.\n";
return false;
#endif

View File

@ -745,7 +745,7 @@ static String format_error_message(DWORD id) {
Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String full_path = "game/" + p_path;
p_library_handle = (void *)LoadPackagedLibrary((LPCWSTR)(full_path.utf16().get_data()), 0);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + ".");
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", full_path, format_error_message(GetLastError())));
if (r_resolved_path != nullptr) {
*r_resolved_path = full_path;

View File

@ -536,7 +536,7 @@ const GodotDisplay = {
}
navigator.clipboard.writeText(text).catch(function (e) {
// Setting OS clipboard is only possible from an input callback.
GodotRuntime.error('Setting OS clipboard is only possible from an input callback for the Web plafrom. Exception:', e);
GodotRuntime.error('Setting OS clipboard is only possible from an input callback for the Web platform. Exception:', e);
});
return 0;
},

View File

@ -232,7 +232,7 @@ bool OS_Web::is_userfs_persistent() const {
Error OS_Web::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = p_path.get_file();
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror());
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
*r_resolved_path = path;

View File

@ -1598,7 +1598,7 @@ void DisplayServerWindows::window_request_attention(WindowID p_window) {
FLASHWINFO info;
info.cbSize = sizeof(FLASHWINFO);
info.hwnd = wd.hWnd;
info.dwFlags = FLASHW_TRAY;
info.dwFlags = FLASHW_ALL;
info.dwTimeout = 0;
info.uCount = 2;
FlashWindowEx(&info);
@ -3926,7 +3926,9 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
WindowRect.top += offset.y;
WindowRect.bottom += offset.y;
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
}
WindowID id = window_id_counter;
{

View File

@ -389,13 +389,13 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han
}
missing += E;
}
ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s, missing dependencies: (%s), error: \"%s\".", p_path, missing, format_error_message(err_code)));
ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Missing dependencies: %s. Error: %s.", p_path, missing, format_error_message(err_code)));
} else {
ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s, error: \"%s\"." + p_path, format_error_message(err_code)));
ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, format_error_message(err_code)));
}
}
#else
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s, error: \"%s\"." + p_path, format_error_message(GetLastError())));
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, format_error_message(GetLastError())));
#endif
if (cookie) {

View File

@ -149,7 +149,7 @@ void GPUParticles2D::set_trail_enabled(bool p_enabled) {
}
void GPUParticles2D::set_trail_lifetime(double p_seconds) {
ERR_FAIL_COND(p_seconds < 0.001);
ERR_FAIL_COND(p_seconds < 0.01);
trail_lifetime = p_seconds;
RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
queue_redraw();

View File

@ -369,6 +369,19 @@ bool TouchScreenButton::is_passby_press_enabled() const {
return passby_press;
}
#ifndef DISABLE_DEPRECATED
bool TouchScreenButton::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == SNAME("normal")) { // Compatibility with Godot 3.x.
set_texture_normal(p_value);
return true;
} else if (p_name == SNAME("pressed")) { // Compatibility with Godot 3.x.
set_texture_pressed(p_value);
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
void TouchScreenButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_texture_normal", "texture"), &TouchScreenButton::set_texture_normal);
ClassDB::bind_method(D_METHOD("get_texture_normal"), &TouchScreenButton::get_texture_normal);

View File

@ -71,6 +71,9 @@ private:
protected:
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
#endif // DISABLE_DEPRECATED
public:
#ifdef TOOLS_ENABLED

View File

@ -170,6 +170,30 @@ Transform3D Camera3D::get_camera_transform() const {
return tr;
}
Projection Camera3D::_get_camera_projection(real_t p_near) const {
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm;
switch (mode) {
case PROJECTION_PERSPECTIVE: {
cm.set_perspective(fov, viewport_size.aspect(), p_near, far, keep_aspect == KEEP_WIDTH);
} break;
case PROJECTION_ORTHOGONAL: {
cm.set_orthogonal(size, viewport_size.aspect(), p_near, far, keep_aspect == KEEP_WIDTH);
} break;
case PROJECTION_FRUSTUM: {
cm.set_frustum(size, viewport_size.aspect(), frustum_offset, p_near, far);
} break;
}
return cm;
}
Projection Camera3D::get_camera_projection() const {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Projection(), "Camera is not inside the scene tree.");
return _get_camera_projection(near);
}
void Camera3D::set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far) {
if (!force_change && fov == p_fovy_degrees && p_z_near == near && p_z_far == far && mode == PROJECTION_PERSPECTIVE) {
return;
@ -286,8 +310,7 @@ Vector3 Camera3D::project_local_ray_normal(const Point2 &p_pos) const {
if (mode == PROJECTION_ORTHOGONAL) {
ray = Vector3(0, 0, -1);
} else {
Projection cm;
cm.set_perspective(fov, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
Projection cm = _get_camera_projection(near);
Vector2 screen_he = cm.get_viewport_half_extents();
ray = Vector3(((cpos.x / viewport_size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (cpos.y / viewport_size.height)) * 2.0 - 1.0) * screen_he.y, -near).normalized();
}
@ -302,9 +325,7 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const {
Vector2 cpos = get_viewport()->get_camera_coords(p_pos);
ERR_FAIL_COND_V(viewport_size.y == 0, Vector3());
if (mode == PROJECTION_PERSPECTIVE) {
return get_camera_transform().origin;
} else {
if (mode == PROJECTION_ORTHOGONAL) {
Vector2 pos = cpos / viewport_size;
real_t vsize, hsize;
if (keep_aspect == KEEP_WIDTH) {
@ -321,6 +342,8 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const {
ray.z = -near;
ray = get_camera_transform().xform(ray);
return ray;
} else {
return get_camera_transform().origin;
};
};
@ -333,15 +356,7 @@ bool Camera3D::is_position_behind(const Vector3 &p_pos) const {
Vector<Vector3> Camera3D::get_near_plane_points() const {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector<Vector3>(), "Camera is not inside scene.");
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm;
if (mode == PROJECTION_ORTHOGONAL) {
cm.set_orthogonal(size, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
} else {
cm.set_perspective(fov, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
}
Projection cm = _get_camera_projection(near);
Vector3 endpoints[8];
cm.get_endpoints(Transform3D(), endpoints);
@ -361,13 +376,7 @@ Point2 Camera3D::unproject_position(const Vector3 &p_pos) const {
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm;
if (mode == PROJECTION_ORTHOGONAL) {
cm.set_orthogonal(size, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
} else {
cm.set_perspective(fov, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
}
Projection cm = _get_camera_projection(near);
Plane p(get_camera_transform().xform_inv(p_pos), 1.0);
@ -389,13 +398,7 @@ Vector3 Camera3D::project_position(const Point2 &p_point, real_t p_z_depth) cons
}
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm;
if (mode == PROJECTION_ORTHOGONAL) {
cm.set_orthogonal(size, viewport_size.aspect(), p_z_depth, far, keep_aspect == KEEP_WIDTH);
} else {
cm.set_perspective(fov, viewport_size.aspect(), p_z_depth, far, keep_aspect == KEEP_WIDTH);
}
Projection cm = _get_camera_projection(p_z_depth);
Vector2 vp_he = cm.get_viewport_half_extents();
@ -508,6 +511,7 @@ void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_current", "enabled"), &Camera3D::set_current);
ClassDB::bind_method(D_METHOD("is_current"), &Camera3D::is_current);
ClassDB::bind_method(D_METHOD("get_camera_transform"), &Camera3D::get_camera_transform);
ClassDB::bind_method(D_METHOD("get_camera_projection"), &Camera3D::get_camera_projection);
ClassDB::bind_method(D_METHOD("get_fov"), &Camera3D::get_fov);
ClassDB::bind_method(D_METHOD("get_frustum_offset"), &Camera3D::get_frustum_offset);
ClassDB::bind_method(D_METHOD("get_size"), &Camera3D::get_size);
@ -653,13 +657,7 @@ bool Camera3D::get_cull_mask_value(int p_layer_number) const {
Vector<Plane> Camera3D::get_frustum() const {
ERR_FAIL_COND_V(!is_inside_world(), Vector<Plane>());
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm;
if (mode == PROJECTION_PERSPECTIVE) {
cm.set_perspective(fov, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
} else {
cm.set_orthogonal(size, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
}
Projection cm = _get_camera_projection(near);
return cm.get_projection_planes(get_camera_transform());
}

View File

@ -105,6 +105,8 @@ protected:
static void _bind_methods();
Projection _get_camera_projection(real_t p_near) const;
public:
enum {
NOTIFICATION_BECAME_CURRENT = 50,
@ -138,6 +140,7 @@ public:
void set_frustum_offset(Vector2 p_offset);
virtual Transform3D get_camera_transform() const;
virtual Projection get_camera_projection() const;
virtual Vector3 project_ray_normal(const Point2 &p_pos) const;
virtual Vector3 project_ray_origin(const Point2 &p_pos) const;

View File

@ -181,7 +181,7 @@ void GPUParticles3D::set_trail_enabled(bool p_enabled) {
}
void GPUParticles3D::set_trail_lifetime(double p_seconds) {
ERR_FAIL_COND(p_seconds < 0.001);
ERR_FAIL_COND(p_seconds < 0.01);
trail_lifetime = p_seconds;
RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
}

View File

@ -647,11 +647,11 @@ SpriteBase3D::SpriteBase3D() {
// Create basic mesh and store format information.
for (int i = 0; i < 4; i++) {
mesh_normals.write[i] = Vector3(0.0, 0.0, 0.0);
mesh_tangents.write[i * 4 + 0] = 0.0;
mesh_normals.write[i] = Vector3(0.0, 0.0, 1.0);
mesh_tangents.write[i * 4 + 0] = 1.0;
mesh_tangents.write[i * 4 + 1] = 0.0;
mesh_tangents.write[i * 4 + 2] = 0.0;
mesh_tangents.write[i * 4 + 3] = 0.0;
mesh_tangents.write[i * 4 + 3] = 1.0;
mesh_colors.write[i] = Color(1.0, 1.0, 1.0, 1.0);
mesh_uvs.write[i] = Vector2(0.0, 0.0);
mesh_vertices.write[i] = Vector3(0.0, 0.0, 0.0);

View File

@ -901,18 +901,20 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
bool is_state_changed = false;
NextInfo next;
StringName transition_start = current;
Vector<StringName> transition_path;
transition_path.push_back(current);
while (true) {
next = _find_next(p_tree, p_state_machine);
if (next.node == transition_start) {
is_state_changed = false;
break; // Maybe infinity loop, do noting more.
if (transition_path.has(next.node)) {
WARN_PRINT_ONCE_ED("AnimationNodeStateMachinePlayback: " + base_path + "playback aborts the transition by detecting one or more looped transitions in the same frame to prevent to infinity loop. You may need to check the transition settings.");
break; // Maybe infinity loop, do nothing more.
}
if (!_can_transition_to_next(p_tree, p_state_machine, next, p_test_only)) {
break; // Finish transition.
}
transition_path.push_back(next.node);
is_state_changed = true;
// Setting for fading.

View File

@ -44,6 +44,12 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) {
}
}
void AcceptDialog::_parent_focused() {
if (close_on_escape && !is_exclusive()) {
_cancel_pressed();
}
}
void AcceptDialog::_update_theme_item_cache() {
Window::_update_theme_item_cache();
@ -64,6 +70,16 @@ void AcceptDialog::_notification(int p_what) {
get_ok_button()->grab_focus();
}
_update_child_rects();
parent_visible = get_parent_visible_window();
if (parent_visible) {
parent_visible->connect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
}
} else {
if (parent_visible) {
parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
}
} break;
@ -76,9 +92,10 @@ void AcceptDialog::_notification(int p_what) {
}
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
if (close_on_escape && !is_exclusive()) {
_cancel_pressed();
case NOTIFICATION_EXIT_TREE: {
if (parent_visible) {
parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
} break;
@ -112,9 +129,21 @@ void AcceptDialog::_ok_pressed() {
}
void AcceptDialog::_cancel_pressed() {
Window *parent_window = parent_visible;
if (parent_visible) {
parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
call_deferred(SNAME("hide"));
emit_signal(SNAME("canceled"));
cancel_pressed();
if (parent_window) {
//parent_window->grab_focus();
}
set_input_as_handled();
}
@ -257,18 +286,29 @@ void AcceptDialog::_custom_action(const String &p_action) {
custom_action(p_action);
}
void AcceptDialog::_custom_button_visibility_changed(Button *button) {
Control *right_spacer = Object::cast_to<Control>(button->get_meta("__right_spacer"));
if (right_spacer) {
right_spacer->set_visible(button->is_visible());
}
}
Button *AcceptDialog::add_button(const String &p_text, bool p_right, const String &p_action) {
Button *button = memnew(Button);
button->set_text(p_text);
Control *right_spacer;
if (p_right) {
buttons_hbox->add_child(button);
buttons_hbox->add_spacer();
right_spacer = buttons_hbox->add_spacer();
} else {
buttons_hbox->add_child(button);
buttons_hbox->move_child(button, 0);
buttons_hbox->add_spacer(true);
right_spacer = buttons_hbox->add_spacer(true);
}
button->set_meta("__right_spacer", right_spacer);
button->connect("visibility_changed", callable_mp(this, &AcceptDialog::_custom_button_visibility_changed).bind(button));
child_controls_changed();
if (is_visible()) {
@ -301,6 +341,12 @@ void AcceptDialog::remove_button(Control *p_button) {
ERR_FAIL_COND_MSG(button->get_parent() != buttons_hbox, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name()));
ERR_FAIL_COND_MSG(button == ok_button, "Cannot remove dialog's OK button.");
Control *right_spacer = Object::cast_to<Control>(button->get_meta("__right_spacer"));
if (right_spacer) {
ERR_FAIL_COND_MSG(right_spacer->get_parent() != buttons_hbox, vformat("Cannot remove button %s as its associated spacer does not belong to this dialog.", button->get_name()));
}
button->disconnect("visibility_changed", callable_mp(this, &AcceptDialog::_custom_button_visibility_changed));
if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) {
button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action));
}
@ -308,11 +354,10 @@ void AcceptDialog::remove_button(Control *p_button) {
button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
}
Node *right_spacer = buttons_hbox->get_child(button->get_index() + 1);
// Should always be valid but let's avoid crashing.
if (right_spacer) {
buttons_hbox->remove_child(right_spacer);
memdelete(right_spacer);
button->remove_meta("__right_spacer");
right_spacer->queue_free();
}
buttons_hbox->remove_child(button);

View File

@ -44,6 +44,8 @@ class LineEdit;
class AcceptDialog : public Window {
GDCLASS(AcceptDialog, Window);
Window *parent_visible = nullptr;
Panel *bg_panel = nullptr;
Label *message_label = nullptr;
HBoxContainer *buttons_hbox = nullptr;
@ -58,11 +60,13 @@ class AcceptDialog : public Window {
} theme_cache;
void _custom_action(const String &p_action);
void _custom_button_visibility_changed(Button *button);
void _update_child_rects();
static bool swap_cancel_ok;
void _input_from_window(const Ref<InputEvent> &p_event);
void _parent_focused();
protected:
virtual Size2 _get_contents_minimum_size() const override;

View File

@ -833,6 +833,8 @@ void FileDialog::set_file_mode(FileMode p_mode) {
} else {
tree->set_select_mode(Tree::SELECT_SINGLE);
}
get_ok_button()->set_disabled(_is_open_should_be_disabled());
}
FileDialog::FileMode FileDialog::get_file_mode() const {

View File

@ -451,7 +451,7 @@ void OptionButton::_refresh_size_cache() {
_cached_size = Vector2();
for (int i = 0; i < get_item_count(); i++) {
_cached_size = _cached_size.max(get_minimum_size_for_text_and_icon(get_item_text(i), get_item_icon(i)));
_cached_size = _cached_size.max(get_minimum_size_for_text_and_icon(popup->get_item_xl_text(i), get_item_icon(i)));
}
update_minimum_size();
}

Some files were not shown because too many files have changed in this diff Show More