diff options
25 files changed, 462 insertions, 44 deletions
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 5c89b64429..d7ca8afc2c 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -4,7 +4,7 @@ Provides methods for file reading and writing operations. </brief_description> <description> - This class can be used to permanently store data in the user device's file system and to read from it. This is useful for store game save data or player configuration files. + This class can be used to permanently store data in the user device's file system and to read from it. This is useful for storing game save data or player configuration files. Here's a sample on how to write and read from a file: [codeblocks] [gdscript] diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 7a1296c411..6294baf294 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2191,7 +2191,7 @@ void AnimationTrackEdit::_notification(int p_what) { offset = offset * scale + limit; Color marker_color = animation->get_marker_color(marker); marker_color.a = 0.2; - draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color); + draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE)); } } } @@ -3647,7 +3647,7 @@ void AnimationTrackEditGroup::_notification(int p_what) { offset = offset * scale + limit; Color marker_color = editor->get_current_animation()->get_marker_color(marker); marker_color.a = 0.2; - draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color); + draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE)); } } } @@ -8307,9 +8307,6 @@ void AnimationMarkerEdit::_notification(int p_what) { Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label")); Color color = get_theme_color(SceneStringName(font_color), SNAME("Label")); - int hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); - Color linecolor = color; - linecolor.a = 0.2; // SECTION PREVIEW // @@ -8368,31 +8365,21 @@ void AnimationMarkerEdit::_notification(int p_what) { draw_key(name, scale, int(offset), is_selected, limit, limit_end); - const int font_size = 16; + const int font_size = 12 * EDSCALE; Size2 string_size = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size); if (int(offset) <= limit_end && int(offset) >= limit && should_show_all_marker_names) { float bottom = get_size().height + string_size.y - font->get_descent(font_size); float extrusion = MAX(0, offset + string_size.x - limit_end); // How much the string would extrude outside limit_end if unadjusted. Color marker_color = animation->get_marker_color(name); - draw_string(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color); - draw_string_outline(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color); + float margin = 4 * EDSCALE; + Point2 pos = Point2(offset - extrusion + margin, bottom + margin); + draw_string(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color); + draw_string_outline(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color); } } } draw_fg(limit, get_size().width - timeline->get_buttons_width()); - - // BUTTONS // - - { - int ofs = get_size().width - timeline->get_buttons_width(); - - draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE)); - - ofs += hsep; - } - - draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); } break; case NOTIFICATION_MOUSE_ENTER: 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 f192bf0fb7..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,6 +429,19 @@ 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; @@ -431,19 +455,56 @@ namespace Godot.SourceGenerators } } + 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; + string? hintString = null; + + if (exportToolButtonAttr != null) + { + if (memberVariantType != VariantType.Callable) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportToolButtonIsNotCallableRule, + memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + memberSymbol.ToDisplayString() + )); + return null; + } + + 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}"; + } + } + + 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/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/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/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index ab5de3cb7f..e9fa38475e 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -946,6 +946,7 @@ RID RenderingDevice::texture_create_shared(const TextureView &p_view, RID p_with RDG::ResourceTracker *tracker = RDG::resource_tracker_create(); tracker->texture_driver_id = texture.shared_fallback->texture; + tracker->texture_size = Size2i(texture.width, texture.height); tracker->texture_subresources = texture.barrier_range(); tracker->texture_usage = alias_format.usage_bits; tracker->reference_count = 1; @@ -1125,6 +1126,7 @@ RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view, RDG::ResourceTracker *tracker = RDG::resource_tracker_create(); tracker->texture_driver_id = texture.shared_fallback->texture; + tracker->texture_size = Size2i(texture.width, texture.height); tracker->texture_subresources = slice_range; tracker->texture_usage = slice_format.usage_bits; tracker->reference_count = 1; @@ -5415,6 +5417,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id draw_tracker = RDG::resource_tracker_create(); draw_tracker->parent = owner_texture->draw_tracker; draw_tracker->texture_driver_id = p_texture->driver_id; + draw_tracker->texture_size = Size2i(p_texture->width, p_texture->height); draw_tracker->texture_subresources = p_texture->barrier_range(); draw_tracker->texture_usage = p_texture->usage_flags; draw_tracker->texture_slice_or_dirty_rect = p_texture->slice_rect; @@ -5437,6 +5440,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id // Regular texture. p_texture->draw_tracker = RDG::resource_tracker_create(); p_texture->draw_tracker->texture_driver_id = p_texture->driver_id; + p_texture->draw_tracker->texture_size = Size2i(p_texture->width, p_texture->height); p_texture->draw_tracker->texture_subresources = p_texture->barrier_range(); p_texture->draw_tracker->texture_usage = p_texture->usage_flags; p_texture->draw_tracker->reference_count = 1; diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp index 86b5f80e56..c8ad0e2722 100644 --- a/servers/rendering/rendering_device_graph.cpp +++ b/servers/rendering/rendering_device_graph.cpp @@ -140,7 +140,7 @@ RDD::BarrierAccessBits RenderingDeviceGraph::_usage_to_access_bits(ResourceUsage #endif } -bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const { +bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const { if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { // We don't check possible intersections for usages that aren't consecutive color or depth writes. return true; @@ -155,15 +155,27 @@ bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resour return true; } - if (!r_intersection_partial_coverage) { - // Indicate if this draw list only partially covers the region of the previous draw list. - r_intersection_partial_coverage = !current_draw_list_command.region.encloses(previous_draw_list_command.region); - } - // We check if the region used by both draw lists have an intersection. return previous_draw_list_command.region.intersects(current_draw_list_command.region); } +bool RenderingDeviceGraph::_check_command_partial_coverage(ResourceTracker *p_resource_tracker, int32_t p_command_index) const { + if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { + // We don't check for partial coverage in usages that aren't attachment writes. + return true; + } + + const uint32_t command_data_offset = command_data_offsets[p_command_index]; + const RecordedDrawListCommand &draw_list_command = *reinterpret_cast<const RecordedDrawListCommand *>(&command_data[command_data_offset]); + if (draw_list_command.type != RecordedCommand::TYPE_DRAW_LIST) { + // We don't check for partial coverage on commands that aren't draw lists. + return false; + } + + Rect2i texture_region(Point2i(0, 0), p_resource_tracker->texture_size); + return !draw_list_command.region.encloses(texture_region); +} + int32_t RenderingDeviceGraph::_add_to_command_list(int32_t p_command_index, int32_t p_list_index) { DEV_ASSERT(p_command_index < int32_t(command_count)); DEV_ASSERT(p_list_index < int32_t(command_list_nodes.size())); @@ -199,7 +211,7 @@ int32_t RenderingDeviceGraph::_add_to_slice_read_list(int32_t p_command_index, R return next_index; } -int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index) { +int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index, bool p_partial_coverage) { DEV_ASSERT(p_command_index < int32_t(command_count)); DEV_ASSERT(p_list_index < int32_t(write_slice_list_nodes.size())); @@ -210,6 +222,7 @@ int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i new_node.command_index = p_command_index; new_node.next_list_index = p_list_index; new_node.subresources = p_subresources; + new_node.partial_coverage = p_partial_coverage; return next_index; } @@ -476,7 +489,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr resource_tracker->usage = new_resource_usage; } - bool intersection_partial_coverage = false; + bool write_usage_has_partial_coverage = !different_usage && _check_command_partial_coverage(resource_tracker, p_command_index); if (search_tracker->write_command_or_list_index >= 0) { if (search_tracker->write_command_list_enabled) { // Make this command adjacent to any commands that wrote to this resource and intersect with the slice if it applies. @@ -488,11 +501,11 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr if (!resource_has_parent || search_tracker_rect.intersects(write_list_node.subresources)) { if (write_list_node.command_index == p_command_index) { ERR_FAIL_COND_MSG(!resource_has_parent, "Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index, intersection_partial_coverage)) { + } else if (!write_list_node.partial_coverage || _check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index)) { // Command is dependent on this command. Add this command to the adjacency list of the write command. _add_adjacent_command(write_list_node.command_index, p_command_index, r_command); - if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources)) { + if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources) && !write_usage_has_partial_coverage) { // Eliminate redundant writes from the list. if (previous_write_list_index >= 0) { RecordedSliceListNode &previous_list_node = write_slice_list_nodes[previous_write_list_index]; @@ -514,22 +527,23 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr // The index is just the latest command index that wrote to the resource. if (search_tracker->write_command_or_list_index == p_command_index) { ERR_FAIL_MSG("Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index, intersection_partial_coverage)) { + } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index)) { _add_adjacent_command(search_tracker->write_command_or_list_index, p_command_index, r_command); } } } if (write_usage) { - if (resource_has_parent || intersection_partial_coverage) { + bool use_write_list = resource_has_parent || write_usage_has_partial_coverage; + if (use_write_list) { if (!search_tracker->write_command_list_enabled && search_tracker->write_command_or_list_index >= 0) { // Write command list was not being used but there was a write command recorded. Add a new node with the entire parent resource's subresources and the recorded command index to the list. const RDD::TextureSubresourceRange &tracker_subresources = search_tracker->texture_subresources; Rect2i tracker_rect(tracker_subresources.base_mipmap, tracker_subresources.base_layer, tracker_subresources.mipmap_count, tracker_subresources.layer_count); - search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1); + search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1, false); } - search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index); + search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index, write_usage_has_partial_coverage); search_tracker->write_command_list_enabled = true; } else { search_tracker->write_command_or_list_index = p_command_index; @@ -554,7 +568,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr read_full_command_list_index = read_full_next_index; } - if (!resource_has_parent) { + if (!use_write_list) { // Clear the full list if this resource is not a slice. search_tracker->read_full_command_list_index = -1; } @@ -564,7 +578,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr int32_t read_slice_command_list_index = search_tracker->read_slice_command_list_index; while (read_slice_command_list_index >= 0) { const RecordedSliceListNode &read_list_node = read_slice_list_nodes[read_slice_command_list_index]; - if (!resource_has_parent || search_tracker_rect.encloses(read_list_node.subresources)) { + if (!use_write_list || search_tracker_rect.encloses(read_list_node.subresources)) { if (previous_slice_command_list_index >= 0) { // Erase this element and connect the previous one to the next element. read_slice_list_nodes[previous_slice_command_list_index].next_list_index = read_list_node.next_list_index; diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h index 452e1700b6..f7d9027147 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -167,6 +167,7 @@ public: RDD::BufferID buffer_driver_id; RDD::TextureID texture_driver_id; RDD::TextureSubresourceRange texture_subresources; + Size2i texture_size; uint32_t texture_usage = 0; int32_t texture_slice_command_index = -1; ResourceTracker *parent = nullptr; @@ -271,6 +272,7 @@ private: int32_t command_index = -1; int32_t next_list_index = -1; Rect2i subresources; + bool partial_coverage = false; }; struct RecordedBufferClearCommand : RecordedCommand { @@ -649,11 +651,12 @@ private: static bool _is_write_usage(ResourceUsage p_usage); static RDD::TextureLayout _usage_to_image_layout(ResourceUsage p_usage); static RDD::BarrierAccessBits _usage_to_access_bits(ResourceUsage p_usage); - bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const; + bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const; + bool _check_command_partial_coverage(ResourceTracker *p_resource_tracker, int32_t p_command_index) const; int32_t _add_to_command_list(int32_t p_command_index, int32_t p_list_index); void _add_adjacent_command(int32_t p_previous_command_index, int32_t p_command_index, RecordedCommand *r_command); int32_t _add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); - int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); + int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index, bool p_partial_coverage); RecordedCommand *_allocate_command(uint32_t p_command_size, int32_t &r_command_index); DrawListInstruction *_allocate_draw_list_instruction(uint32_t p_instruction_size); ComputeListInstruction *_allocate_compute_list_instruction(uint32_t p_instruction_size); |
