summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp14
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/gdscript_editor.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp4
-rw-r--r--modules/gltf/gltf_document.cpp2
-rw-r--r--modules/mono/csharp_script.cpp2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs27
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs48
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs48
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs48
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs48
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs12
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs30
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs82
-rw-r--r--modules/mono/editor/bindings_generator.cpp24
-rw-r--r--modules/mono/editor/code_completion.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs33
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj1
-rw-r--r--modules/mono/utils/path_utils.cpp2
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp4
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp10
-rw-r--r--modules/multiplayer/multiplayer_spawner.h1
-rw-r--r--modules/svg/image_loader_svg.cpp2
32 files changed, 451 insertions, 50 deletions
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index 8ca5dba225..be28d89508 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -38,13 +38,12 @@
#include <transcoder/basisu_transcoder.h>
#ifdef TOOLS_ENABLED
#include <encoder/basisu_comp.h>
-#endif
-void basis_universal_init() {
-#ifdef TOOLS_ENABLED
- basisu::basisu_encoder_init();
+static Mutex init_mutex;
+static bool initialized = false;
#endif
+void basis_universal_init() {
basist::basisu_transcoder_init();
}
@@ -80,6 +79,13 @@ inline void _basisu_pad_mipmap(const uint8_t *p_image_mip_data, Vector<uint8_t>
}
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) {
+ init_mutex.lock();
+ if (!initialized) {
+ basisu::basisu_encoder_init();
+ initialized = true;
+ }
+ init_mutex.unlock();
+
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
Ref<Image> image = p_image->duplicate();
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 758887a723..3a5a88d356 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -140,7 +140,7 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
r_enum = String(p_gdtype.native_type).replace("::", ".");
if (r_enum.begins_with("res://")) {
r_enum = r_enum.trim_prefix("res://");
- int dot_pos = r_enum.rfind(".");
+ int dot_pos = r_enum.rfind_char('.');
if (dot_pos >= 0) {
r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
}
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 0b12f2ff76..629581bd6c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -163,7 +163,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
- if (str.find("\\", from) >= 0) {
+ if (str.find_char('\\', from) >= 0) {
break;
}
}
@@ -236,7 +236,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
for (; from < line_length; from++) {
if (line_length - from < end_key_length) {
// Don't break if '\' to highlight esc chars.
- if (str.find("\\", from) < 0) {
+ if (str.find_char('\\', from) < 0) {
break;
}
}
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index a29bd1fdb5..d58cd2c3f7 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3472,7 +3472,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
String arg = arg_itr->name;
if (arg.contains(":")) {
- arg = arg.substr(0, arg.find(":"));
+ arg = arg.substr(0, arg.find_char(':'));
}
method_hint += arg;
if (use_type_hint) {
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 2db46adef4..5ba9ee3fa6 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -80,7 +80,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
}
pipe = pipe.substr(bl);
pipe = pipe.replace_first("Blender ", "");
- int pp = pipe.find(".");
+ int pp = pipe.find_char('.');
if (pp == -1) {
if (r_err) {
*r_err = vformat(TTR("Couldn't extract version information from Blender executable at: %s."), p_path);
@@ -96,7 +96,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
return false;
}
- int pp2 = pipe.find(".", pp + 1);
+ int pp2 = pipe.find_char('.', pp + 1);
r_minor = pp2 > pp ? pipe.substr(pp + 1, pp2 - pp - 1).to_int() : 0;
return true;
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 7cac61304f..2f36c29ec4 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -689,7 +689,7 @@ void GLTFDocument::_compute_node_heights(Ref<GLTFState> p_state) {
}
static Vector<uint8_t> _parse_base64_uri(const String &p_uri) {
- int start = p_uri.find(",");
+ int start = p_uri.find_char(',');
ERR_FAIL_COND_V(start == -1, Vector<uint8_t>());
CharString substr = p_uri.substr(start + 1).ascii();
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 380b401683..ec9c123f8d 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2817,7 +2817,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
if (p_path.begins_with("csharp://")) {
// This is a virtual path used by generic types, extract the real path.
real_path = "res://" + p_path.trim_prefix("csharp://");
- real_path = real_path.substr(0, real_path.rfind(":"));
+ real_path = real_path.substr(0, real_path.rfind_char(':'));
}
Ref<CSharpScript> scr;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs
index 6425c723dd..fe511d67ef 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs
@@ -74,4 +74,31 @@ public class ExportDiagnosticsTests
}
);
}
+
+ [Fact]
+ public async void ExportToolButtonInNonToolClass()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ new string[] { "ExportDiagnostics_GD0108.cs" },
+ new string[] { "ExportDiagnostics_GD0108_ScriptProperties.generated.cs" }
+ );
+ }
+
+ [Fact]
+ public async void ExportAndExportToolButtonOnSameMember()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ new string[] { "ExportDiagnostics_GD0109.cs" },
+ new string[] { "ExportDiagnostics_GD0109_ScriptProperties.generated.cs" }
+ );
+ }
+
+ [Fact]
+ public async void ExportToolButtonOnNonCallable()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ new string[] { "ExportDiagnostics_GD0110.cs" },
+ new string[] { "ExportDiagnostics_GD0110_ScriptProperties.generated.cs" }
+ );
+ }
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs
index 724fb164e0..9693cba9ce 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs
@@ -66,4 +66,13 @@ public class ScriptPropertiesGeneratorTests
"AbstractGenericNode(Of T)_ScriptProperties.generated.cs"
);
}
+
+ [Fact]
+ public async void ExportedButtons()
+ {
+ await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
+ "ExportedToolButtons.cs",
+ "ExportedToolButtons_ScriptProperties.generated.cs"
+ );
+ }
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..77114a7c93
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs
@@ -0,0 +1,48 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ExportDiagnostics_GD0108
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.Node.PropertyName {
+ /// <summary>
+ /// Cached name for the 'MyButton' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName @MyButton = "MyButton";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.@MyButton) {
+ this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.@MyButton) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..fc4547f2c1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs
@@ -0,0 +1,48 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ExportDiagnostics_GD0109
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.Node.PropertyName {
+ /// <summary>
+ /// Cached name for the 'MyButton' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName @MyButton = "MyButton";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.@MyButton) {
+ this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.@MyButton) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..d1aac17557
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs
@@ -0,0 +1,48 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ExportDiagnostics_GD0110
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.Node.PropertyName {
+ /// <summary>
+ /// Cached name for the 'MyButton' field.
+ /// </summary>
+ public new static readonly global::Godot.StringName @MyButton = "MyButton";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
+ {
+ if (name == PropertyName.@MyButton) {
+ this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value);
+ return true;
+ }
+ return base.SetGodotClassPropertyValue(name, value);
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.@MyButton) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@MyButton);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs
new file mode 100644
index 0000000000..6e4ad1f13e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs
@@ -0,0 +1,48 @@
+using Godot;
+using Godot.NativeInterop;
+
+partial class ExportedToolButtons
+{
+#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
+ /// <summary>
+ /// Cached StringNames for the properties and fields contained in this class, for fast lookup.
+ /// </summary>
+ public new class PropertyName : global::Godot.GodotObject.PropertyName {
+ /// <summary>
+ /// Cached name for the 'MyButton1' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName @MyButton1 = "MyButton1";
+ /// <summary>
+ /// Cached name for the 'MyButton2' property.
+ /// </summary>
+ public new static readonly global::Godot.StringName @MyButton2 = "MyButton2";
+ }
+ /// <inheritdoc/>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
+ {
+ if (name == PropertyName.@MyButton1) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton1);
+ return true;
+ }
+ if (name == PropertyName.@MyButton2) {
+ value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton2);
+ return true;
+ }
+ return base.GetGodotClassPropertyValue(name, out value);
+ }
+ /// <summary>
+ /// Get the property information for all the properties declared in this class.
+ /// This method is used by Godot to register the available properties in the editor.
+ /// Do not call this method.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
+ {
+ var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
+ properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton1, hint: (global::Godot.PropertyHint)39, hintString: "Click me!", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton2, hint: (global::Godot.PropertyHint)39, hintString: "Click me!,ColorRect", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
+ return properties;
+ }
+#pragma warning restore CS0109
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs
new file mode 100644
index 0000000000..6fde2e5957
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs
@@ -0,0 +1,8 @@
+using Godot;
+using Godot.Collections;
+
+public partial class ExportDiagnostics_GD0108 : Node
+{
+ [ExportToolButton("")]
+ public Callable {|GD0108:MyButton|};
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs
new file mode 100644
index 0000000000..80a04cd641
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs
@@ -0,0 +1,9 @@
+using Godot;
+using Godot.Collections;
+
+[Tool]
+public partial class ExportDiagnostics_GD0109 : Node
+{
+ [Export, ExportToolButton("")]
+ public Callable {|GD0109:MyButton|};
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs
new file mode 100644
index 0000000000..586bf6c19e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs
@@ -0,0 +1,9 @@
+using Godot;
+using Godot.Collections;
+
+[Tool]
+public partial class ExportDiagnostics_GD0110 : Node
+{
+ [ExportToolButton("")]
+ public string {|GD0110:MyButton|};
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs
new file mode 100644
index 0000000000..91c14387c5
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs
@@ -0,0 +1,12 @@
+using Godot;
+using System;
+
+[Tool]
+public partial class ExportedToolButtons : GodotObject
+{
+ [ExportToolButton("Click me!")]
+ public Callable MyButton1 => Callable.From(() => { GD.Print("Clicked MyButton1!"); });
+
+ [ExportToolButton("Click me!", Icon = "ColorRect")]
+ public Callable MyButton2 => Callable.From(() => { GD.Print("Clicked MyButton2!"); });
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md
index 7286853f7a..90c8491177 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -3,3 +3,6 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0003.html)
+GD0108 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0108.html)
+GD0109 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0109.html)
+GD0110 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0110.html)
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
index ad7962e7df..8da0818e42 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -107,6 +107,36 @@ namespace Godot.SourceGenerators
"Types not derived from Node should not export Node members. Node export is only supported in Node-derived classes.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0107"));
+ public static readonly DiagnosticDescriptor OnlyToolClassesShouldUseExportToolButtonRule =
+ new DiagnosticDescriptor(id: "GD0108",
+ title: "The exported tool button is not in a tool class",
+ messageFormat: "The exported tool button '{0}' is not in a tool class",
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ "The exported tool button is not in a tool class. Annotate the class with the '[Tool]' attribute, or remove the '[ExportToolButton]' attribute.",
+ helpLinkUri: string.Format(_helpLinkFormat, "GD0108"));
+
+ public static readonly DiagnosticDescriptor ExportToolButtonShouldNotBeUsedWithExportRule =
+ new DiagnosticDescriptor(id: "GD0109",
+ title: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute",
+ messageFormat: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute on '{0}'",
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ "The '[ExportToolButton]' attribute cannot be used with the '[Export]' attribute. Remove one of the attributes.",
+ helpLinkUri: string.Format(_helpLinkFormat, "GD0109"));
+
+ public static readonly DiagnosticDescriptor ExportToolButtonIsNotCallableRule =
+ new DiagnosticDescriptor(id: "GD0110",
+ title: "The exported tool button is not a Callable",
+ messageFormat: "The exported tool button '{0}' is not a Callable",
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ "The exported tool button is not a Callable. The '[ExportToolButton]' attribute is only supported on members of type Callable.",
+ helpLinkUri: string.Format(_helpLinkFormat, "GD0110"));
+
public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule =
new DiagnosticDescriptor(id: "GD0201",
title: "The name of the delegate must end with 'EventHandler'",
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index 62fa7b0a36..46c446169a 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -287,6 +287,12 @@ namespace Godot.SourceGenerators
public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr;
+ public static bool IsGodotExportToolButtonAttribute(this INamedTypeSymbol symbol)
+ => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportToolButtonAttr;
+
+ public static bool IsGodotToolAttribute(this INamedTypeSymbol symbol)
+ => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ToolAttr;
+
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
index af39a54b0b..f6de09e25b 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
@@ -9,10 +9,12 @@ namespace Godot.SourceGenerators
public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute";
public const string ExportGroupAttr = "Godot.ExportGroupAttribute";
public const string ExportSubgroupAttr = "Godot.ExportSubgroupAttribute";
+ public const string ExportToolButtonAttr = "Godot.ExportToolButtonAttribute";
public const string SignalAttr = "Godot.SignalAttribute";
public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute";
public const string GodotClassNameAttr = "Godot.GodotClassNameAttribute";
public const string GlobalClassAttr = "Godot.GlobalClassAttribute";
+ public const string ToolAttr = "Godot.ToolAttribute";
public const string SystemFlagsAttr = "System.FlagsAttribute";
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
index bb4c4824e7..d77ebc3cec 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -89,7 +89,8 @@ namespace Godot.SourceGenerators
Password = 36,
LayersAvoidance = 37,
DictionaryType = 38,
- Max = 39
+ ToolButton = 39,
+ Max = 40
}
[Flags]
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index fc67e4f592..ed78353f92 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -69,6 +69,7 @@ namespace Godot.SourceGenerators
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
+ bool isToolClass = symbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotToolAttribute() ?? false);
string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptProperties.generated";
@@ -277,6 +278,16 @@ namespace Godot.SourceGenerators
if (propertyInfo == null)
continue;
+ if (propertyInfo.Value.Hint == PropertyHint.ToolButton && !isToolClass)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Common.OnlyToolClassesShouldUseExportToolButtonRule,
+ member.Symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
+ member.Symbol.ToDisplayString()
+ ));
+ continue;
+ }
+
AppendPropertyInfo(source, propertyInfo.Value);
}
@@ -418,47 +429,82 @@ namespace Godot.SourceGenerators
var exportAttr = memberSymbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false);
+ var exportToolButtonAttr = memberSymbol.GetAttributes()
+ .FirstOrDefault(a => a.AttributeClass?.IsGodotExportToolButtonAttribute() ?? false);
+
+ if (exportAttr != null && exportToolButtonAttr != null)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Common.ExportToolButtonShouldNotBeUsedWithExportRule,
+ memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
+ memberSymbol.ToDisplayString()
+ ));
+ return null;
+ }
+
var propertySymbol = memberSymbol as IPropertySymbol;
var fieldSymbol = memberSymbol as IFieldSymbol;
if (exportAttr != null && propertySymbol != null)
{
- if (propertySymbol.GetMethod == null)
+ if (propertySymbol.GetMethod == null || propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
{
- // This should never happen, as we filtered WriteOnly properties, but just in case.
- context.ReportDiagnostic(Diagnostic.Create(
- Common.ExportedPropertyIsWriteOnlyRule,
- propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
- propertySymbol.ToDisplayString()
- ));
+ // Exports can be neither read-only nor write-only but the diagnostic errors for properties are already
+ // reported by ScriptPropertyDefValGenerator.cs so just quit early here.
return null;
}
+ }
+
+ if (exportToolButtonAttr != null && propertySymbol != null && propertySymbol.GetMethod == null)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Common.ExportedPropertyIsWriteOnlyRule,
+ propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
+ propertySymbol.ToDisplayString()
+ ));
+ return null;
+ }
+
+ var memberType = propertySymbol?.Type ?? fieldSymbol!.Type;
+
+ var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
+ string memberName = memberSymbol.Name;
- if (propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
+ string? hintString = null;
+
+ if (exportToolButtonAttr != null)
+ {
+ if (memberVariantType != VariantType.Callable)
{
- // This should never happen, as we filtered ReadOnly properties, but just in case.
context.ReportDiagnostic(Diagnostic.Create(
- Common.ExportedMemberIsReadOnlyRule,
- propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
- propertySymbol.ToDisplayString()
+ Common.ExportToolButtonIsNotCallableRule,
+ memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
+ memberSymbol.ToDisplayString()
));
return null;
}
- }
- var memberType = propertySymbol?.Type ?? fieldSymbol!.Type;
+ hintString = exportToolButtonAttr.ConstructorArguments[0].Value?.ToString() ?? "";
+ foreach (var namedArgument in exportToolButtonAttr.NamedArguments)
+ {
+ if (namedArgument is { Key: "Icon", Value.Value: string { Length: > 0 } })
+ {
+ hintString += $",{namedArgument.Value.Value}";
+ }
+ }
- var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
- string memberName = memberSymbol.Name;
+ return new PropertyInfo(memberVariantType, memberName, PropertyHint.ToolButton,
+ hintString: hintString, PropertyUsageFlags.Editor, exported: true);
+ }
if (exportAttr == null)
{
return new PropertyInfo(memberVariantType, memberName, PropertyHint.None,
- hintString: null, PropertyUsageFlags.ScriptVariable, exported: false);
+ hintString: hintString, PropertyUsageFlags.ScriptVariable, exported: false);
}
if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType,
- isTypeArgument: false, out var hint, out var hintString))
+ isTypeArgument: false, out var hint, out hintString))
{
var constructorArguments = exportAttr.ConstructorArguments;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index db90ac5a6e..c54d58d6a0 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -190,7 +190,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
int pos = 0;
while (pos < bbcode.length()) {
- int brk_pos = bbcode.find("[", pos);
+ int brk_pos = bbcode.find_char('[', pos);
if (brk_pos < 0) {
brk_pos = bbcode.length();
@@ -210,7 +210,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
break;
}
- int brk_end = bbcode.find("]", brk_pos + 1);
+ int brk_end = bbcode.find_char(']', brk_pos + 1);
if (brk_end == -1) {
String text = bbcode.substr(brk_pos, bbcode.length() - brk_pos);
@@ -239,7 +239,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
output.append("[");
pos = brk_pos + 1;
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
- const int tag_end = tag.find(" ");
+ const int tag_end = tag.find_char(' ');
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -385,7 +385,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "url") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -403,7 +403,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
pos = brk_end + 1;
tag_stack.push_front("url");
} else if (tag == "img") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -455,7 +455,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
int pos = 0;
while (pos < bbcode.length()) {
- int brk_pos = bbcode.find("[", pos);
+ int brk_pos = bbcode.find_char('[', pos);
if (brk_pos < 0) {
brk_pos = bbcode.length();
@@ -488,7 +488,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
break;
}
- int brk_end = bbcode.find("]", brk_pos + 1);
+ int brk_end = bbcode.find_char(']', brk_pos + 1);
if (brk_end == -1) {
if (!line_del) {
@@ -551,7 +551,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append("[");
pos = brk_pos + 1;
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
- const int tag_end = tag.find(" ");
+ const int tag_end = tag.find_char(' ');
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -696,7 +696,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "code" || tag.begins_with("code ")) {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -751,7 +751,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "url") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -772,7 +772,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front("url");
} else if (tag == "img") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -1619,7 +1619,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
bool enum_in_static_class = false;
- if (enum_proxy_name.find(".") > 0) {
+ if (enum_proxy_name.find_char('.') > 0) {
enum_in_static_class = true;
String enum_class_name = enum_proxy_name.get_slicec('.', 0);
enum_proxy_name = enum_proxy_name.get_slicec('.', 1);
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
index ae914e71ef..94222f3d67 100644
--- a/modules/mono/editor/code_completion.cpp
+++ b/modules/mono/editor/code_completion.cpp
@@ -116,7 +116,7 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr
continue;
}
- String name = prop.name.substr(prop.name.find("/") + 1, prop.name.length());
+ String name = prop.name.substr(prop.name.find_char('/') + 1, prop.name.length());
suggestions.push_back(quoted(name));
}
} break;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs
new file mode 100644
index 0000000000..87a9e628f9
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs
@@ -0,0 +1,33 @@
+using System;
+
+#nullable enable
+
+namespace Godot
+{
+ /// <summary>
+ /// Exports the annotated <see cref="Callable"/> as a clickable button.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class ExportToolButtonAttribute : Attribute
+ {
+ /// <summary>
+ /// The label of the button.
+ /// </summary>
+ public string Text { get; }
+
+ /// <summary>
+ /// If defined, used to fetch an icon for the button via <see cref="Control.GetThemeIcon"/>,
+ /// from the <code>EditorIcons</code> theme type.
+ /// </summary>
+ public string? Icon { get; init; }
+
+ /// <summary>
+ /// Exports the annotated <see cref="Callable"/> as a clickable button.
+ /// </summary>
+ /// <param name="text">The label of the button.</param>
+ public ExportToolButtonAttribute(string text)
+ {
+ Text = text;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 3a3134d160..5aa68559d8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -48,6 +48,7 @@
<!-- Sources -->
<ItemGroup>
<Compile Include="Core\Aabb.cs" />
+ <Compile Include="Core\Attributes\ExportToolButtonAttribute.cs" />
<Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
<Compile Include="Core\Bridge\MethodInfo.cs" />
<Compile Include="Core\Callable.generics.cs" />
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index ee17a668d7..068ac8b4e1 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -212,7 +212,7 @@ String relative_to_impl(const String &p_path, const String &p_relative_to) {
#ifdef WINDOWS_ENABLED
String get_drive_letter(const String &p_norm_path) {
int idx = p_norm_path.find(":/");
- if (idx != -1 && idx < p_norm_path.find("/")) {
+ if (idx != -1 && idx < p_norm_path.find_char('/')) {
return p_norm_path.substr(0, idx + 1);
}
return String();
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index 8de82ef409..4d5480eebf 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -375,7 +375,7 @@ void ReplicationEditor::_add_pressed() {
return;
}
- int idx = np_text.find(":");
+ int idx = np_text.find_char(':');
if (idx == -1) {
np_text = ".:" + np_text;
} else if (idx == 0) {
@@ -554,7 +554,7 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
Ref<Texture2D> icon = _get_class_icon(root_node);
if (root_node) {
- String path = prop.substr(0, prop.find(":"));
+ String path = prop.substr(0, prop.find_char(':'));
String subpath = prop.substr(path.size());
Node *node = root_node->get_node_or_null(path);
if (!node) {
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 682d20022f..69a7545e51 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -97,7 +97,13 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
void MultiplayerSpawner::add_spawnable_scene(const String &p_path) {
SpawnableScene sc;
- sc.path = p_path;
+ if (p_path.begins_with("uid://")) {
+ sc.uid = p_path;
+ sc.path = ResourceUID::uid_to_path(p_path);
+ } else {
+ sc.uid = ResourceUID::path_to_uid(p_path);
+ sc.path = p_path;
+ }
if (Engine::get_singleton()->is_editor_hint()) {
ERR_FAIL_COND(!ResourceLoader::exists(p_path));
}
@@ -139,7 +145,7 @@ Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const {
Vector<String> ss;
ss.resize(spawnable_scenes.size());
for (int i = 0; i < ss.size(); i++) {
- ss.write[i] = spawnable_scenes[i].path;
+ ss.write[i] = spawnable_scenes[i].uid;
}
return ss;
}
diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h
index 0e94b781ea..868efb9e0b 100644
--- a/modules/multiplayer/multiplayer_spawner.h
+++ b/modules/multiplayer/multiplayer_spawner.h
@@ -49,6 +49,7 @@ public:
private:
struct SpawnableScene {
String path;
+ String uid;
Ref<PackedScene> cache;
};
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index b9d493b844..1f7d5504b6 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -53,7 +53,7 @@ void ImageLoaderSVG::_replace_color_property(const HashMap<Color, Color> &p_colo
int pos = r_string.find(p_prefix);
while (pos != -1) {
pos += prefix_len; // Skip prefix.
- int end_pos = r_string.find("\"", pos);
+ int end_pos = r_string.find_char('"', pos);
ERR_FAIL_COND_MSG(end_pos == -1, vformat("Malformed SVG string after property \"%s\".", p_prefix));
const String color_code = r_string.substr(pos, end_pos - pos);
if (color_code != "none" && !color_code.begins_with("url(")) {