1
0
Fork 0

Merge branch 'master' of https://github.com/godotengine/godot into feat-cam-mjpeg

This commit is contained in:
Philip Whitfield 2025-02-17 13:58:17 +01:00
commit 3010c78b18
184 changed files with 2447 additions and 2026 deletions

View File

@ -222,6 +222,23 @@ uint64_t OS::get_embedded_pck_offset() const {
return 0;
}
// Default boot screen rect scale mode is "Keep Aspect Centered"
Rect2 OS::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const {
Rect2 screenrect;
if (p_window_size.width > p_window_size.height) {
// Scale horizontally.
screenrect.size.y = p_window_size.height;
screenrect.size.x = p_imgrect_size.x * p_window_size.height / p_imgrect_size.y;
screenrect.position.x = (p_window_size.width - screenrect.size.x) / 2;
} else {
// Scale vertically.
screenrect.size.x = p_window_size.width;
screenrect.size.y = p_imgrect_size.y * p_window_size.width / p_imgrect_size.x;
screenrect.position.y = (p_window_size.height - screenrect.size.y) / 2;
}
return screenrect;
}
// Helper function to ensure that a dir name/path will be valid on the OS
String OS::get_safe_dir_name(const String &p_dir_name, bool p_allow_paths) const {
String safe_dir_name = p_dir_name;

View File

@ -161,6 +161,8 @@ public:
virtual void open_midi_inputs();
virtual void close_midi_inputs();
virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const;
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
struct GDExtensionData {

View File

@ -299,12 +299,12 @@ String TranslationServer::get_locale_name(const String &p_locale) const {
}
}
String name = language_map[lang_name];
String name = get_language_name(lang_name);
if (!script_name.is_empty()) {
name = name + " (" + script_map[script_name] + ")";
name = name + " (" + get_script_name(script_name) + ")";
}
if (!country_name.is_empty()) {
name = name + ", " + country_name_map[country_name];
name = name + ", " + get_country_name(country_name);
}
return name;
}
@ -320,7 +320,11 @@ Vector<String> TranslationServer::get_all_languages() const {
}
String TranslationServer::get_language_name(const String &p_language) const {
return language_map[p_language];
if (language_map.has(p_language)) {
return language_map[p_language];
} else {
return p_language;
}
}
Vector<String> TranslationServer::get_all_scripts() const {
@ -334,7 +338,11 @@ Vector<String> TranslationServer::get_all_scripts() const {
}
String TranslationServer::get_script_name(const String &p_script) const {
return script_map[p_script];
if (script_map.has(p_script)) {
return script_map[p_script];
} else {
return p_script;
}
}
Vector<String> TranslationServer::get_all_countries() const {
@ -348,7 +356,11 @@ Vector<String> TranslationServer::get_all_countries() const {
}
String TranslationServer::get_country_name(const String &p_country) const {
return country_name_map[p_country];
if (country_name_map.has(p_country)) {
return country_name_map[p_country];
} else {
return p_country;
}
}
void TranslationServer::set_locale(const String &p_locale) {

View File

@ -219,7 +219,7 @@
</member>
<member name="grow" type="bool" setter="set_grow_enabled" getter="is_grow_enabled" default="false">
If [code]true[/code], enables the vertex grow setting. This can be used to create mesh-based outlines using a second material pass and its [member cull_mode] set to [constant CULL_FRONT]. See also [member grow_amount].
[b]Note:[/b] Vertex growth cannot create new vertices, which means that visible gaps may occur in sharp corners. This can be alleviated by designing the mesh to use smooth normals exclusively using [url=https://wiki.polycount.com/wiki/Face_weighted_normals]face weighted normals[/url] in the 3D authoring software. In this case, grow will be able to join every outline together, just like in the original mesh.
[b]Note:[/b] Vertex growth cannot create new vertices, which means that visible gaps may occur in sharp corners. This can be alleviated by designing the mesh to use smooth normals exclusively using [url=http://wiki.polycount.com/wiki/Face_weighted_normals]face weighted normals[/url] in the 3D authoring software. In this case, grow will be able to join every outline together, just like in the original mesh.
</member>
<member name="grow_amount" type="float" setter="set_grow" getter="get_grow" default="0.0">
Grows object vertices in the direction of their normals. Only effective if [member grow] is [code]true[/code].

View File

@ -1372,7 +1372,7 @@
Automatic layout direction, determined from the parent control layout direction.
</constant>
<constant name="LAYOUT_DIRECTION_APPLICATION_LOCALE" value="1" enum="LayoutDirection">
Automatic layout direction, determined from the current locale. Right-to-left layout direction is automatically used for languages that require it such as Arabic and Hebrew., but only if a valid translation file is loaded for the given language. For all other languages (or if no valid translation file is found by Godot), left-to-right layout direction is used. If using [TextServerFallback] ([member ProjectSettings.internationalization/rendering/text_driver]), left-to-right layout direction is always used regardless of the language.
Automatic layout direction, determined from the current locale. Right-to-left layout direction is automatically used for languages that require it such as Arabic and Hebrew, but only if a valid translation file is loaded for the given language (unless said language is configured as a fallback in [member ProjectSettings.internationalization/locale/fallback]). For all other languages (or if no valid translation file is found by Godot), left-to-right layout direction is used. If using [TextServerFallback] ([member ProjectSettings.internationalization/rendering/text_driver]), left-to-right layout direction is always used regardless of the language. Right-to-left layout direction can also be forced using [member ProjectSettings.internationalization/rendering/force_right_to_left_layout_direction].
</constant>
<constant name="LAYOUT_DIRECTION_LTR" value="2" enum="LayoutDirection">
Left-to-right layout direction.

View File

@ -314,13 +314,14 @@
The maximum number of steps for screen-space reflections. Higher values are slower.
</member>
<member name="tonemap_exposure" type="float" setter="set_tonemap_exposure" getter="get_tonemap_exposure" default="1.0">
The default exposure used for tonemapping. Higher values result in a brighter image. See also [member tonemap_white].
Adjusts the brightness of values before they are provided to the tonemapper. Higher [member tonemap_exposure] values result in a brighter image. See also [member tonemap_white].
[b]Note:[/b] Values provided to the tonemapper will also be multiplied by [code]2.0[/code] and [code]1.8[/code] for [constant TONE_MAPPER_FILMIC] and [constant TONE_MAPPER_ACES] respectively to produce a similar apparent brightness as [constant TONE_MAPPER_LINEAR].
</member>
<member name="tonemap_mode" type="int" setter="set_tonemapper" getter="get_tonemapper" enum="Environment.ToneMapper" default="0">
The tonemapping mode to use. Tonemapping is the process that "converts" HDR values to be suitable for rendering on an LDR display. (Godot doesn't support rendering on HDR displays yet.)
</member>
<member name="tonemap_white" type="float" setter="set_tonemap_white" getter="get_tonemap_white" default="1.0">
The white reference value for tonemapping (also called "whitepoint"). Higher values can make highlights look less blown out, and will also slightly darken the whole scene as a result. See also [member tonemap_exposure].
The white reference value for tonemapping, which indicates where bright white is located in the scale of values provided to the tonemapper. For photorealistic lighting, recommended values are between [code]6.0[/code] and [code]8.0[/code]. Higher values result in less blown out highlights, but may make the scene appear lower contrast. See also [member tonemap_exposure].
[b]Note:[/b] [member tonemap_white] is ignored when using [constant TONE_MAPPER_LINEAR] or [constant TONE_MAPPER_AGX].
</member>
<member name="volumetric_fog_albedo" type="Color" setter="set_volumetric_fog_albedo" getter="get_volumetric_fog_albedo" default="Color(1, 1, 1, 1)">
@ -414,20 +415,22 @@
Use the [Sky] for reflections regardless of what the background is.
</constant>
<constant name="TONE_MAPPER_LINEAR" value="0" enum="ToneMapper">
Linear tonemapper operator. Reads the linear data and passes it on unmodified. This can cause bright lighting to look blown out, with noticeable clipping in the output colors.
Does not modify color data, resulting in a linear tonemapping curve which unnaturally clips bright values, causing bright lighting to look blown out. The simplest and fastest tonemapper.
</constant>
<constant name="TONE_MAPPER_REINHARDT" value="1" enum="ToneMapper">
Reinhard tonemapper operator. Performs a variation on rendered pixels' colors by this formula: [code]color = color * (1 + color / (white * white)) / (1 + color)[/code]. This avoids clipping bright highlights, but the resulting image can look a bit dull. When [member tonemap_white] is left at the default value of [code]1.0[/code] this is identical to [constant TONE_MAPPER_LINEAR] while also being slightly less performant.
A simple tonemapping curve that rolls off bright values to prevent clipping. This results in an image that can appear dull and low contrast. Slower than [constant TONE_MAPPER_LINEAR].
[b]Note:[/b] When [member tonemap_white] is left at the default value of [code]1.0[/code], [constant TONE_MAPPER_REINHARDT] produces an identical image to [constant TONE_MAPPER_LINEAR].
</constant>
<constant name="TONE_MAPPER_FILMIC" value="2" enum="ToneMapper">
Filmic tonemapper operator. This avoids clipping bright highlights, with a resulting image that usually looks more vivid than [constant TONE_MAPPER_REINHARDT].
Uses a film-like tonemapping curve to prevent clipping of bright values and provide better contrast than [constant TONE_MAPPER_REINHARDT]. Slightly slower than [constant TONE_MAPPER_REINHARDT].
</constant>
<constant name="TONE_MAPPER_ACES" value="3" enum="ToneMapper">
Use the Academy Color Encoding System tonemapper. ACES is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. ACES typically has a more contrasted output compared to [constant TONE_MAPPER_REINHARDT] and [constant TONE_MAPPER_FILMIC].
Uses a high-contrast film-like tonemapping curve and desaturates bright values for a more realistic appearance. Slightly slower than [constant TONE_MAPPER_FILMIC].
[b]Note:[/b] This tonemapping operator is called "ACES Fitted" in Godot 3.x.
</constant>
<constant name="TONE_MAPPER_AGX" value="4" enum="ToneMapper">
Use the AgX tonemapper. AgX is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. AgX is less likely to darken parts of the scene compared to [constant TONE_MAPPER_ACES] and can match the overall scene brightness of [constant TONE_MAPPER_FILMIC] more closely.
Uses a film-like tonemapping curve and desaturates bright values for a more realistic appearance. Better than other tonemappers at maintaining the hue of colors as they become brighter. The slowest tonemapping option.
[b]Note:[/b] [member tonemap_white] is fixed at a value of [code]16.29[/code], which makes [constant TONE_MAPPER_AGX] unsuitable for use with the Mobile rendering method.
</constant>
<constant name="GLOW_BLEND_MODE_ADDITIVE" value="0" enum="GlowBlendMode">
Additive glow blending mode. Mostly used for particles, glows (bloom), lens flare, bright sources.

View File

@ -469,7 +469,7 @@
</signal>
<signal name="frame_rect_changed">
<param index="0" name="frame" type="GraphFrame" />
<param index="1" name="new_rect" type="Vector2" />
<param index="1" name="new_rect" type="Rect2" />
<description>
Emitted when the [GraphFrame] [param frame] is resized to [param new_rect].
</description>

View File

@ -143,7 +143,7 @@
<param index="3" name="body" type="String" default="&quot;&quot;" />
<description>
Sends a request to the connected host.
The URL parameter is usually just the part after the host, so for [code]https://somehost.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]).
The URL parameter is usually just the part after the host, so for [code]https://example.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]).
Headers are HTTP request headers. For available HTTP methods, see [enum Method].
To create a POST request with query strings to push to the server, do:
[codeblocks]
@ -171,7 +171,7 @@
<param index="3" name="body" type="PackedByteArray" />
<description>
Sends a raw request to the connected host.
The URL parameter is usually just the part after the host, so for [code]https://somehost.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]).
The URL parameter is usually just the part after the host, so for [code]https://example.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]).
Headers are HTTP request headers. For available HTTP methods, see [enum Method].
Sends the body data raw, as a byte array and does not encode it in any way.
</description>

View File

@ -90,7 +90,7 @@
http_request.request_completed.connect(self._http_request_completed)
# Perform the HTTP request. The URL below returns a PNG image as of writing.
var error = http_request.request("https://via.placeholder.com/512")
var error = http_request.request("https://placehold.co/512")
if error != OK:
push_error("An error occurred in the HTTP request.")
@ -120,7 +120,7 @@
httpRequest.RequestCompleted += HttpRequestCompleted;
// Perform the HTTP request. The URL below returns a PNG image as of writing.
Error error = httpRequest.Request("https://via.placeholder.com/512");
Error error = httpRequest.Request("https://placehold.co/512");
if (error != Error.Ok)
{
GD.PushError("An error occurred in the HTTP request.");

View File

@ -15,10 +15,18 @@
print(datetime.format(formatter))
[/codeblock]
[b]Warning:[/b] When calling Java methods, be sure to check [method JavaClassWrapper.get_exception] to check if the method threw an exception.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_exception">
<return type="JavaObject" />
<description>
Returns the Java exception from the last call into a Java class. If there was no exception, it will return [code]null[/code].
[b]Note:[/b] This method only works on Android. On every other platform, this method will always return [code]null[/code].
</description>
</method>
<method name="wrap">
<return type="JavaClass" />
<param index="0" name="name" type="String" />

View File

@ -1429,9 +1429,14 @@
<member name="input_devices/pen_tablet/driver" type="String" setter="" getter="">
Specifies the tablet driver to use. If left empty, the default driver will be used.
[b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
[b]Note:[/b] Use [method DisplayServer.tablet_set_current_driver] to switch tablet driver in runtime.
</member>
<member name="input_devices/pen_tablet/driver.windows" type="String" setter="" getter="">
Override for [member input_devices/pen_tablet/driver] on Windows.
Override for [member input_devices/pen_tablet/driver] on Windows. Supported values are:
- [code]auto[/code] (default), uses [code]wintab[/code] if Windows Ink is disabled in the Wacom Tablet Properties or system settings, [code]winink[/code] otherwise.
- [code]winink[/code], uses Windows native "Windows Ink" driver.
- [code]wintab[/code], uses Wacom "WinTab" driver.
- [code]dummy[/code], tablet input is disabled.
</member>
<member name="input_devices/pointing/android/enable_long_press_as_right_click" type="bool" setter="" getter="" default="false">
If [code]true[/code], long press events on an Android touchscreen are transformed into right click events.
@ -1480,7 +1485,7 @@
The expansion ratio to use during pseudolocalization. A value of [code]0.3[/code] is sufficient for most practical purposes, and will increase the length of each string by 30%.
</member>
<member name="internationalization/pseudolocalization/fake_bidi" type="bool" setter="" getter="" default="false">
If [code]true[/code], emulate bidirectional (right-to-left) text when pseudolocalization is enabled. This can be used to spot issues with RTL layout and UI mirroring that will crop up if the project is localized to RTL languages such as Arabic or Hebrew.
If [code]true[/code], emulate bidirectional (right-to-left) text when pseudolocalization is enabled. This can be used to spot issues with RTL layout and UI mirroring that will crop up if the project is localized to RTL languages such as Arabic or Hebrew. See also [member internationalization/rendering/force_right_to_left_layout_direction].
</member>
<member name="internationalization/pseudolocalization/override" type="bool" setter="" getter="" default="false">
Replace all characters in the string with [code]*[/code]. Useful for finding non-localizable strings.
@ -1502,7 +1507,7 @@
[b]Note:[/b] This property is only read when the project starts. To toggle pseudolocalization at run-time, use [member TranslationServer.pseudolocalization_enabled] instead.
</member>
<member name="internationalization/rendering/force_right_to_left_layout_direction" type="bool" setter="" getter="" default="false">
Force layout direction and text writing direction to RTL for all controls.
Force layout direction and text writing direction to RTL for all controls, even if the current locale is intended to use a left-to-right layout and text writing direction. This should be enabled for testing purposes only. See also [member internationalization/pseudolocalization/fake_bidi].
</member>
<member name="internationalization/rendering/root_node_auto_translate" type="bool" setter="" getter="" default="true">
If [code]true[/code], root node will use [constant Node.AUTO_TRANSLATE_MODE_ALWAYS], otherwise [constant Node.AUTO_TRANSLATE_MODE_DISABLED] will be used.
@ -2199,6 +2204,10 @@
The CA certificates bundle to use for TLS connections. If this is set to a non-empty value, this will [i]override[/i] Godot's default [url=https://github.com/godotengine/godot/blob/master/thirdparty/certs/ca-certificates.crt]Mozilla certificate bundle[/url]. If left empty, the default certificate bundle will be used.
If in doubt, leave this setting empty.
</member>
<member name="network/tls/enable_tls_v1.3" type="bool" setter="" getter="" default="false">
If [code]true[/code], enable TLSv1.3 negotiation.
[b]Note:[/b] This is experimental, and may cause connections to fail in some cases (notably, if the remote server uses TLS handshake fragmentation).
</member>
<member name="physics/2d/default_angular_damp" type="float" setter="" getter="" default="1.0">
The default rotational motion damping in 2D. Damping is used to gradually slow down physical objects over time. RigidBodies will fall back to this value when combining their own damping values and no area damping value is present.
Suggested values are in the range [code]0[/code] to [code]30[/code]. At value [code]0[/code] objects will keep moving with the same velocity. Greater values will stop the object faster. A value equal to or greater than the physics tick rate ([member physics/common/physics_ticks_per_second]) will bring the object to a stop in one iteration.

View File

@ -703,11 +703,10 @@
<param index="1" name="format" type="int" enum="RenderingDevice.IndexBufferFormat" />
<param index="2" name="data" type="PackedByteArray" default="PackedByteArray()" />
<param index="3" name="use_restart_indices" type="bool" default="false" />
<param index="4" name="enable_device_address" type="bool" default="false" />
<param index="4" name="creation_bits" type="int" enum="RenderingDevice.BufferCreationBits" is_bitfield="true" default="0" />
<description>
Creates a new index buffer. It can be accessed with the RID that is returned.
Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method.
Optionally, set [param enable_device_address] if you wish to use [method buffer_get_device_address] functionality and the GPU supports it.
</description>
</method>
<method name="limit_get" qualifiers="const">
@ -847,6 +846,7 @@
<param index="0" name="size_bytes" type="int" />
<param index="1" name="data" type="PackedByteArray" default="PackedByteArray()" />
<param index="2" name="usage" type="int" enum="RenderingDevice.StorageBufferUsage" is_bitfield="true" default="0" />
<param index="3" name="creation_bits" type="int" enum="RenderingDevice.BufferCreationBits" is_bitfield="true" default="0" />
<description>
Creates a [url=https://vkguide.dev/docs/chapter-4/storage_buffers/]storage buffer[/url] with the specified [param data] and [param usage]. It can be accessed with the RID that is returned.
Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method.
@ -1073,11 +1073,10 @@
<return type="RID" />
<param index="0" name="size_bytes" type="int" />
<param index="1" name="data" type="PackedByteArray" default="PackedByteArray()" />
<param index="2" name="enable_device_address" type="bool" default="false" />
<param index="2" name="creation_bits" type="int" enum="RenderingDevice.BufferCreationBits" is_bitfield="true" default="0" />
<description>
Creates a new uniform buffer. It can be accessed with the RID that is returned.
Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method.
Optionally, set [param enable_device_address] if you wish to use [method buffer_get_device_address] functionality and the GPU supports it.
</description>
</method>
<method name="uniform_set_create">
@ -1111,12 +1110,10 @@
<return type="RID" />
<param index="0" name="size_bytes" type="int" />
<param index="1" name="data" type="PackedByteArray" default="PackedByteArray()" />
<param index="2" name="use_as_storage" type="bool" default="false" />
<param index="3" name="enable_device_address" type="bool" default="false" />
<param index="2" name="creation_bits" type="int" enum="RenderingDevice.BufferCreationBits" is_bitfield="true" default="0" />
<description>
It can be accessed with the RID that is returned.
Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method.
Optionally, set [param enable_device_address] if you wish to use [method buffer_get_device_address] functionality and the GPU supports it.
</description>
</method>
<method name="vertex_format_create">
@ -2068,8 +2065,20 @@
</constant>
<constant name="STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT" value="1" enum="StorageBufferUsage" is_bitfield="true">
</constant>
<constant name="STORAGE_BUFFER_USAGE_DEVICE_ADDRESS" value="2" enum="StorageBufferUsage" is_bitfield="true">
Allows usage of [method buffer_get_device_address] on supported GPUs.
<constant name="BUFFER_CREATION_DEVICE_ADDRESS_BIT" value="1" enum="BufferCreationBits" is_bitfield="true">
Optionally, set this flag if you wish to use [method buffer_get_device_address] functionality. You must first check the GPU supports it:
[codeblocks]
[gdscript]
rd = RenderingServer.get_rendering_device()
if rd.has_feature(RenderingDevice.SUPPORTS_BUFFER_DEVICE_ADDRESS):
storage_buffer = rd.storage_buffer_create(bytes.size(), bytes, RenderingDevice.STORAGE_BUFFER_USAGE_SHADER_DEVICE_ADDRESS):
storage_buffer_address = rd.buffer_get_device_address(storage_buffer)
[/gdscript]
[/codeblocks]
</constant>
<constant name="BUFFER_CREATION_AS_STORAGE_BIT" value="2" enum="BufferCreationBits" is_bitfield="true">
Set this flag so that it is created as storage. This is useful if Compute Shaders need access (for reading or writing) to the buffer, e.g. skeletal animations are processed in Compute Shaders which need access to vertex buffers, to be later consumed by vertex shaders as part of the regular rasterization pipeline.
</constant>
<constant name="UNIFORM_TYPE_SAMPLER" value="0" enum="UniformType">
Sampler uniform.

View File

@ -5393,20 +5393,22 @@
Use a simple fog model defined by start and end positions and a custom curve. While not physically accurate, this model can be useful when you need more artistic control.
</constant>
<constant name="ENV_TONE_MAPPER_LINEAR" value="0" enum="EnvironmentToneMapper">
Output color as they came in. This can cause bright lighting to look blown out, with noticeable clipping in the output colors.
Does not modify color data, resulting in a linear tonemapping curve which unnaturally clips bright values, causing bright lighting to look blown out. The simplest and fastest tonemapper.
</constant>
<constant name="ENV_TONE_MAPPER_REINHARD" value="1" enum="EnvironmentToneMapper">
Use the Reinhard tonemapper. Performs a variation on rendered pixels' colors by this formula: [code]color = color * (1 + color / (white * white)) / (1 + color)[/code]. This avoids clipping bright highlights, but the resulting image can look a bit dull. When [member Environment.tonemap_white] is left at the default value of [code]1.0[/code] this is identical to [constant ENV_TONE_MAPPER_LINEAR] while also being slightly less performant.
A simple tonemapping curve that rolls off bright values to prevent clipping. This results in an image that can appear dull and low contrast. Slower than [constant ENV_TONE_MAPPER_LINEAR].
[b]Note:[/b] When [member Environment.tonemap_white] is left at the default value of [code]1.0[/code], [constant ENV_TONE_MAPPER_REINHARD] produces an identical image to [constant ENV_TONE_MAPPER_LINEAR].
</constant>
<constant name="ENV_TONE_MAPPER_FILMIC" value="2" enum="EnvironmentToneMapper">
Use the filmic tonemapper. This avoids clipping bright highlights, with a resulting image that usually looks more vivid than [constant ENV_TONE_MAPPER_REINHARD].
Uses a film-like tonemapping curve to prevent clipping of bright values and provide better contrast than [constant ENV_TONE_MAPPER_REINHARD]. Slightly slower than [constant ENV_TONE_MAPPER_REINHARD].
</constant>
<constant name="ENV_TONE_MAPPER_ACES" value="3" enum="EnvironmentToneMapper">
Use the Academy Color Encoding System tonemapper. ACES is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. ACES typically has a more contrasted output compared to [constant ENV_TONE_MAPPER_REINHARD] and [constant ENV_TONE_MAPPER_FILMIC].
Uses a high-contrast film-like tonemapping curve and desaturates bright values for a more realistic appearance. Slightly slower than [constant ENV_TONE_MAPPER_FILMIC].
[b]Note:[/b] This tonemapping operator is called "ACES Fitted" in Godot 3.x.
</constant>
<constant name="ENV_TONE_MAPPER_AGX" value="4" enum="EnvironmentToneMapper">
Use the AgX tonemapper. AgX is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. AgX is less likely to darken parts of the scene compared to [constant ENV_TONE_MAPPER_ACES], and can match [constant ENV_TONE_MAPPER_FILMIC] more closely.
Uses a film-like tonemapping curve and desaturates bright values for a more realistic appearance. Better than other tonemappers at maintaining the hue of colors as they become brighter. The slowest tonemapping option.
[b]Note:[/b] [member Environment.tonemap_white] is fixed at a value of [code]16.29[/code], which makes [constant ENV_TONE_MAPPER_AGX] unsuitable for use with the Mobile rendering method.
</constant>
<constant name="ENV_SSR_ROUGHNESS_QUALITY_DISABLED" value="0" enum="EnvironmentSSRRoughnessQuality">
Lowest quality of roughness filter for screen-space reflections. Rough materials will not have blurrier screen-space reflections compared to smooth (non-rough) materials. This is the fastest option.

View File

@ -7,6 +7,7 @@
Node for 2D tile-based maps. Tilemaps use a [TileSet] which contain a list of tiles which are used to create grid-based maps. A TileMap may have several layers, layouting tiles on top of each other.
For performance reasons, all TileMap updates are batched at the end of a frame. Notably, this means that scene tiles from a [TileSetScenesCollectionSource] may be initialized after their parent. This is only queued when inside the scene tree.
To force an update earlier on, call [method update_internals].
[b]Note:[/b] For performance and compatibility reasons, the coordinates serialized by [TileMap] are limited to 16-bit signed integers, i.e. the range for X and Y coordinates is from [code]-32768[/code] to [code]32767[/code]. When saving tile data, tiles outside this range are wrapped.
</description>
<tutorials>
<link title="Using Tilemaps">$DOCS_URL/tutorials/2d/using_tilemaps.html</link>

View File

@ -7,6 +7,7 @@
Node for 2D tile-based maps. A [TileMapLayer] uses a [TileSet] which contain a list of tiles which are used to create grid-based maps. Unlike the [TileMap] node, which is deprecated, [TileMapLayer] has only one layer of tiles. You can use several [TileMapLayer] to achieve the same result as a [TileMap] node.
For performance reasons, all TileMap updates are batched at the end of a frame. Notably, this means that scene tiles from a [TileSetScenesCollectionSource] may be initialized after their parent. This is only queued when inside the scene tree.
To force an update earlier on, call [method update_internals].
[b]Note:[/b] For performance and compatibility reasons, the coordinates serialized by [TileMapLayer] are limited to 16-bit signed integers, i.e. the range for X and Y coordinates is from [code]-32768[/code] to [code]32767[/code]. When saving tile data, tiles outside this range are wrapped.
</description>
<tutorials>
<link title="Using Tilemaps">$DOCS_URL/tutorials/2d/using_tilemaps.html</link>

View File

@ -67,7 +67,7 @@
<return type="float" />
<param index="0" name="to" type="Vector2" />
<description>
Returns the angle to the given vector, in radians.
Returns the signed angle to the given vector, in radians.
[url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/vector2_angle_to.png]Illustration of the returned angle.[/url]
</description>
</method>

View File

@ -2238,6 +2238,8 @@ def format_text_block(
repl_text = target_name
if target_class_name != state.current_class:
repl_text = f"{target_class_name}.{target_name}"
if tag_state.name == "method":
repl_text = f"{repl_text}()"
tag_text = f":ref:`{repl_text}<class_{sanitize_class_name(target_class_name)}{ref_type}_{target_name}>`"
escape_pre = True
escape_post = True

View File

@ -6234,6 +6234,8 @@ uint64_t RenderingDeviceDriverD3D12::limit_get(Limit p_limit) {
case LIMIT_VRS_MAX_FRAGMENT_WIDTH:
case LIMIT_VRS_MAX_FRAGMENT_HEIGHT:
return vrs_capabilities.ss_max_fragment_size;
case LIMIT_MAX_SHADER_VARYINGS:
return MIN(D3D12_VS_OUTPUT_REGISTER_COUNT, D3D12_PS_INPUT_REGISTER_COUNT);
default: {
#ifdef DEV_ENABLED
WARN_PRINT("Returning maximum value for unknown limit " + itos(p_limit) + ".");

View File

@ -110,6 +110,11 @@ Config::Config() {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, max_viewport_size);
glGetInteger64v(GL_MAX_UNIFORM_BLOCK_SIZE, &max_uniform_buffer_size);
GLint max_vertex_output;
glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, &max_vertex_output);
GLint max_fragment_input;
glGetIntegerv(GL_MAX_FRAGMENT_INPUT_COMPONENTS, &max_fragment_input);
max_shader_varyings = (uint32_t)MIN(max_vertex_output, max_fragment_input) / 4;
// sanity clamp buffer size to 16K..1MB
max_uniform_buffer_size = CLAMP(max_uniform_buffer_size, 16384, 1048576);

View File

@ -62,6 +62,7 @@ public:
GLint max_texture_size = 0;
GLint max_viewport_size[2] = { 0, 0 };
GLint64 max_uniform_buffer_size = 0;
uint32_t max_shader_varyings = 0;
int64_t max_renderable_elements = 0;
int64_t max_renderable_lights = 0;

View File

@ -465,4 +465,16 @@ Size2i Utilities::get_maximum_viewport_size() const {
return Size2i(config->max_viewport_size[0], config->max_viewport_size[1]);
}
uint32_t Utilities::get_maximum_shader_varyings() const {
Config *config = Config::get_singleton();
ERR_FAIL_NULL_V(config, 31);
return config->max_shader_varyings;
}
uint64_t Utilities::get_maximum_uniform_buffer_size() const {
Config *config = Config::get_singleton();
ERR_FAIL_NULL_V(config, 65536);
return uint64_t(config->max_uniform_buffer_size);
}
#endif // GLES3_ENABLED

View File

@ -226,6 +226,8 @@ public:
virtual String get_video_adapter_api_version() const override;
virtual Size2i get_maximum_viewport_size() const override;
virtual uint32_t get_maximum_shader_varyings() const override;
virtual uint64_t get_maximum_uniform_buffer_size() const override;
};
} // namespace GLES3

View File

@ -124,6 +124,7 @@ struct MetalLimits {
uint32_t maxVertexInputBindings;
uint32_t maxVertexInputBindingStride;
uint32_t maxDrawIndexedIndexValue;
uint32_t maxShaderVaryings;
double temporalScalerInputContentMinScale;
double temporalScalerInputContentMaxScale;

View File

@ -303,6 +303,7 @@ void MetalDeviceProperties::init_limits(id<MTLDevice> p_device) {
limits.maxVertexInputAttributes = 31;
limits.maxVertexInputBindings = 31;
limits.maxVertexInputBindingStride = (2 * KIBI);
limits.maxShaderVaryings = 31; // Accurate on Apple4 and above. See: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
limits.minUniformBufferOffsetAlignment = 64;

View File

@ -3999,6 +3999,8 @@ uint64_t RenderingDeviceDriverMetal::limit_get(Limit p_limit) {
return (uint64_t)((1.0 / limits.temporalScalerInputContentMaxScale) * 1000'000);
case LIMIT_METALFX_TEMPORAL_SCALER_MAX_SCALE:
return (uint64_t)((1.0 / limits.temporalScalerInputContentMinScale) * 1000'000);
case LIMIT_MAX_SHADER_VARYINGS:
return limits.maxShaderVaryings;
UNKNOWN(LIMIT_VRS_TEXEL_WIDTH);
UNKNOWN(LIMIT_VRS_TEXEL_HEIGHT);
UNKNOWN(LIMIT_VRS_MAX_FRAGMENT_WIDTH);

View File

@ -1522,6 +1522,9 @@ RDD::BufferID RenderingDeviceDriverVulkan::buffer_create(uint64_t p_size, BitFie
create_info.usage = p_usage;
create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaMemoryUsage vma_usage = VMA_MEMORY_USAGE_UNKNOWN;
uint32_t vma_flags_to_remove = 0;
VmaAllocationCreateInfo alloc_create_info = {};
switch (p_allocation_type) {
case MEMORY_ALLOCATION_TYPE_CPU: {
@ -1531,15 +1534,19 @@ RDD::BufferID RenderingDeviceDriverVulkan::buffer_create(uint64_t p_size, BitFie
// Looks like a staging buffer: CPU maps, writes sequentially, then GPU copies to VRAM.
alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
vma_flags_to_remove |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
}
if (is_dst && !is_src) {
// Looks like a readback buffer: GPU copies from VRAM, then CPU maps and reads.
alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
vma_flags_to_remove |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
}
vma_usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
alloc_create_info.requiredFlags = (VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
} break;
case MEMORY_ALLOCATION_TYPE_GPU: {
vma_usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
alloc_create_info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
if (p_size <= SMALL_ALLOCATION_MAX_SIZE) {
uint32_t mem_type_index = 0;
@ -1553,12 +1560,19 @@ RDD::BufferID RenderingDeviceDriverVulkan::buffer_create(uint64_t p_size, BitFie
VmaAllocation allocation = nullptr;
VmaAllocationInfo alloc_info = {};
VkResult err = vkCreateBuffer(vk_device, &create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER), &vk_buffer);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't create buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
err = vmaAllocateMemoryForBuffer(allocator, vk_buffer, &alloc_create_info, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't allocate memory for buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
err = vmaBindBufferMemory2(allocator, allocation, 0, vk_buffer, nullptr);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't bind memory to buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
if (!Engine::get_singleton()->is_extra_gpu_memory_tracking_enabled()) {
alloc_create_info.preferredFlags &= ~vma_flags_to_remove;
alloc_create_info.usage = vma_usage;
VkResult err = vmaCreateBuffer(allocator, &create_info, &alloc_create_info, &vk_buffer, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't create buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
} else {
VkResult err = vkCreateBuffer(vk_device, &create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER), &vk_buffer);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't create buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
err = vmaAllocateMemoryForBuffer(allocator, vk_buffer, &alloc_create_info, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't allocate memory for buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
err = vmaBindBufferMemory2(allocator, allocation, 0, vk_buffer, nullptr);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't bind memory to buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
}
// Bookkeep.
BufferInfo *buf_info = VersatileResource::allocate<BufferInfo>(resources_allocator);
@ -1593,8 +1607,12 @@ void RenderingDeviceDriverVulkan::buffer_free(BufferID p_buffer) {
vkDestroyBufferView(vk_device, buf_info->vk_view, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER_VIEW));
}
vkDestroyBuffer(vk_device, buf_info->vk_buffer, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER));
vmaFreeMemory(allocator, buf_info->allocation.handle);
if (!Engine::get_singleton()->is_extra_gpu_memory_tracking_enabled()) {
vmaDestroyBuffer(allocator, buf_info->vk_buffer, buf_info->allocation.handle);
} else {
vkDestroyBuffer(vk_device, buf_info->vk_buffer, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER));
vmaFreeMemory(allocator, buf_info->allocation.handle);
}
VersatileResource::free(resources_allocator, buf_info);
}
@ -1808,12 +1826,18 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat &
VmaAllocation allocation = nullptr;
VmaAllocationInfo alloc_info = {};
VkResult err = vkCreateImage(vk_device, &create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE), &vk_image);
ERR_FAIL_COND_V_MSG(err, TextureID(), "vkCreateImage failed with error " + itos(err) + ".");
err = vmaAllocateMemoryForImage(allocator, vk_image, &alloc_create_info, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, TextureID(), "Can't allocate memory for image, error: " + itos(err) + ".");
err = vmaBindImageMemory2(allocator, allocation, 0, vk_image, nullptr);
ERR_FAIL_COND_V_MSG(err, TextureID(), "Can't bind memory to image, error: " + itos(err) + ".");
if (!Engine::get_singleton()->is_extra_gpu_memory_tracking_enabled()) {
alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VkResult err = vmaCreateImage(allocator, &create_info, &alloc_create_info, &vk_image, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, TextureID(), "vmaCreateImage failed with error " + itos(err) + ".");
} else {
VkResult err = vkCreateImage(vk_device, &create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE), &vk_image);
ERR_FAIL_COND_V_MSG(err, TextureID(), "vkCreateImage failed with error " + itos(err) + ".");
err = vmaAllocateMemoryForImage(allocator, vk_image, &alloc_create_info, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, TextureID(), "Can't allocate memory for image, error: " + itos(err) + ".");
err = vmaBindImageMemory2(allocator, allocation, 0, vk_image, nullptr);
ERR_FAIL_COND_V_MSG(err, TextureID(), "Can't bind memory to image, error: " + itos(err) + ".");
}
// Create view.
@ -1845,10 +1869,15 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat &
}
VkImageView vk_image_view = VK_NULL_HANDLE;
err = vkCreateImageView(vk_device, &image_view_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW), &vk_image_view);
VkResult err = vkCreateImageView(vk_device, &image_view_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW), &vk_image_view);
if (err) {
vkDestroyImage(vk_device, vk_image, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE));
vmaFreeMemory(allocator, allocation);
if (!Engine::get_singleton()->is_extra_gpu_memory_tracking_enabled()) {
vmaDestroyImage(allocator, vk_image, allocation);
} else {
vkDestroyImage(vk_device, vk_image, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE));
vmaFreeMemory(allocator, allocation);
}
ERR_FAIL_COND_V_MSG(err, TextureID(), "vkCreateImageView failed with error " + itos(err) + ".");
}
@ -2023,8 +2052,12 @@ void RenderingDeviceDriverVulkan::texture_free(TextureID p_texture) {
TextureInfo *tex_info = (TextureInfo *)p_texture.id;
vkDestroyImageView(vk_device, tex_info->vk_view, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW));
if (tex_info->allocation.handle) {
vkDestroyImage(vk_device, tex_info->vk_image, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER));
vmaFreeMemory(allocator, tex_info->allocation.handle);
if (!Engine::get_singleton()->is_extra_gpu_memory_tracking_enabled()) {
vmaDestroyImage(allocator, tex_info->vk_view_create_info.image, tex_info->allocation.handle);
} else {
vkDestroyImage(vk_device, tex_info->vk_image, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_BUFFER));
vmaFreeMemory(allocator, tex_info->allocation.handle);
}
}
VersatileResource::free(resources_allocator, tex_info);
}
@ -5886,6 +5919,10 @@ uint64_t RenderingDeviceDriverVulkan::limit_get(Limit p_limit) {
return vrs_capabilities.max_fragment_size.x;
case LIMIT_VRS_MAX_FRAGMENT_HEIGHT:
return vrs_capabilities.max_fragment_size.y;
case LIMIT_MAX_SHADER_VARYINGS:
// The Vulkan spec states that built in varyings like gl_FragCoord should count against this, but in
// practice, that doesn't seem to be the case. The validation layers don't even complain.
return MIN(limits.maxVertexOutputComponents / 4, limits.maxFragmentInputComponents / 4);
default:
ERR_FAIL_V(0);
}

View File

@ -308,6 +308,8 @@ void FileAccessWindows::seek_end(int64_t p_position) {
}
uint64_t FileAccessWindows::get_position() const {
ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
int64_t aux_position = _ftelli64(f);
if (aux_position < 0) {
check_errors();

View File

@ -39,6 +39,7 @@
#include "editor/multi_node_edit.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_plugin.h"
#include "scene/property_utils.h"
#include "scene/resources/packed_scene.h"
void EditorSelectionHistory::cleanup_history() {
@ -536,15 +537,18 @@ Variant EditorData::instantiate_custom_type(const String &p_type, const String &
if (get_custom_types()[p_inherits][i].name == p_type) {
Ref<Script> script = get_custom_types()[p_inherits][i].script;
Variant ob = ClassDB::instantiate(p_inherits);
ERR_FAIL_COND_V(!ob, Variant());
// Store in a variant to initialize the refcount if needed.
Variant v = ClassDB::instantiate(p_inherits);
ERR_FAIL_COND_V(!v, Variant());
Object *ob = v;
Node *n = Object::cast_to<Node>(ob);
if (n) {
n->set_name(p_type);
}
n->set_meta(SceneStringName(_custom_type_script), script);
((Object *)ob)->set_script(script);
return ob;
PropertyUtils::assign_custom_type_script(ob, script);
ob->set_script(script);
return v;
}
}
}
@ -988,12 +992,13 @@ Variant EditorData::script_class_instance(const String &p_class) {
Ref<Script> script = script_class_load_script(p_class);
if (script.is_valid()) {
// Store in a variant to initialize the refcount if needed.
Variant obj = ClassDB::instantiate(script->get_instance_base_type());
if (obj) {
Object::cast_to<Object>(obj)->set_meta(SceneStringName(_custom_type_script), script);
obj.operator Object *()->set_script(script);
Variant v = ClassDB::instantiate(script->get_instance_base_type());
if (v) {
Object *obj = v;
PropertyUtils::assign_custom_type_script(obj, script);
obj->set_script(script);
}
return obj;
return v;
}
}
return Variant();
@ -1041,24 +1046,23 @@ void EditorData::script_class_set_name(const String &p_path, const StringName &p
_script_class_file_to_path[p_path] = p_class;
}
void EditorData::script_class_save_icon_paths() {
Array script_classes = ProjectSettings::get_singleton()->get_global_class_list();
Dictionary d;
for (const KeyValue<StringName, String> &E : _script_class_icon_paths) {
if (ScriptServer::is_global_class(E.key)) {
d[E.key] = E.value;
}
void EditorData::script_class_save_global_classes() {
List<StringName> global_classes;
ScriptServer::get_global_class_list(&global_classes);
Array array_classes;
for (const StringName &class_name : global_classes) {
Dictionary d;
String *icon = _script_class_icon_paths.getptr(class_name);
d["class"] = class_name;
d["language"] = ScriptServer::get_global_class_language(class_name);
d["path"] = ScriptServer::get_global_class_path(class_name);
d["base"] = ScriptServer::get_global_class_base(class_name);
d["icon"] = icon ? *icon : String();
d["is_abstract"] = ScriptServer::is_global_class_abstract(class_name);
d["is_tool"] = ScriptServer::is_global_class_tool(class_name);
array_classes.push_back(d);
}
for (int i = 0; i < script_classes.size(); i++) {
Dictionary d2 = script_classes[i];
if (!d2.has("class")) {
continue;
}
d2["icon"] = d.get(d2["class"], "");
}
ProjectSettings::get_singleton()->store_global_class_list(script_classes);
ProjectSettings::get_singleton()->store_global_class_list(array_classes);
}
void EditorData::script_class_load_icon_paths() {

View File

@ -251,7 +251,7 @@ public:
String script_class_get_icon_path(const String &p_class, bool *r_valid = nullptr) const;
void script_class_set_icon_path(const String &p_class, const String &p_icon_path);
void script_class_clear_icon_paths() { _script_class_icon_paths.clear(); }
void script_class_save_icon_paths();
void script_class_save_global_classes();
void script_class_load_icon_paths();
Ref<Texture2D> extension_class_get_icon(const String &p_class) const;

View File

@ -319,7 +319,12 @@ void EditorFileSystem::_first_scan_filesystem() {
_first_scan_process_scripts(first_scan_root_dir, gdextension_extensions, existing_class_names, extensions);
// Removing invalid global class to prevent having invalid paths in ScriptServer.
_remove_invalid_global_class_names(existing_class_names);
bool save_scripts = _remove_invalid_global_class_names(existing_class_names);
// If a global class is found or removed, we sync global_script_class_cache.cfg with the ScriptServer
if (!existing_class_names.is_empty() || save_scripts) {
EditorNode::get_editor_data().script_class_save_global_classes();
}
// Processing extensions to add new extensions or remove invalid ones.
// Important to do it in the first scan so custom types, new class names, custom importers, etc...
@ -1618,7 +1623,7 @@ void EditorFileSystem::_thread_func_sources(void *_userdata) {
efs->scanning_changes_done.set();
}
void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names) {
bool EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names) {
List<StringName> global_classes;
bool must_save = false;
ScriptServer::get_global_class_list(&global_classes);
@ -1628,9 +1633,7 @@ void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String>
must_save = true;
}
}
if (must_save) {
ScriptServer::save_global_classes();
}
return must_save;
}
String EditorFileSystem::_get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info) {
@ -2043,6 +2046,7 @@ EditorFileSystem::ScriptClassInfo EditorFileSystem::_get_global_script_class(con
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
info.name = ScriptServer::get_language(i)->get_global_class_name(p_path, &info.extends, &info.icon_path, &info.is_abstract, &info.is_tool);
break;
}
}
return info;
@ -2130,8 +2134,7 @@ void EditorFileSystem::_update_script_classes() {
update_script_paths.clear();
}
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
EditorNode::get_editor_data().script_class_save_global_classes();
emit_signal("script_classes_updated");

View File

@ -374,7 +374,7 @@ class EditorFileSystem : public Node {
void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr);
void _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
bool _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
String _get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info);
void _register_global_class_script(const String &p_search_path, const String &p_target_path, const ScriptClassInfoUpdate &p_script_update);

View File

@ -1017,7 +1017,8 @@ void EditorHelp::_update_doc() {
class_desc->add_text(nbsp); // Otherwise icon borrows hyperlink from `_add_type()`.
_add_type(inherits);
inherits = doc->class_list[inherits].inherits;
const DocData::ClassDoc *base_class_doc = doc->class_list.getptr(inherits);
inherits = base_class_doc ? base_class_doc->inherits : String();
if (!inherits.is_empty()) {
class_desc->add_text(" < ");
@ -3713,7 +3714,8 @@ void EditorHelpBit::_update_labels() {
_add_type_to_title({ inherits, String(), false });
inherits = class_list[inherits].inherits;
const DocData::ClassDoc *base_class_doc = class_list.getptr(inherits);
inherits = base_class_doc ? base_class_doc->inherits : String();
}
title->pop(); // font_size

View File

@ -414,6 +414,9 @@ void EditorProperty::_notification(int p_what) {
int ofs = get_theme_constant(SNAME("font_offset"));
int text_limit = text_size - ofs;
int base_spacing = EDITOR_GET("interface/theme/base_spacing");
int padding = base_spacing * EDSCALE;
int half_padding = padding / 2;
if (checkable) {
Ref<Texture2D> checkbox;
@ -444,19 +447,31 @@ void EditorProperty::_notification(int p_what) {
if (can_revert && !is_read_only()) {
Ref<Texture2D> reload_icon = get_editor_theme_icon(SNAME("ReloadSmall"));
text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
revert_rect = Rect2(ofs + text_limit, (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height());
text_limit -= reload_icon->get_width() + half_padding + get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
revert_rect = Rect2(ofs + text_limit, 0, reload_icon->get_width() + padding + (1 * EDSCALE), size.height);
Point2 rtl_pos = Point2();
if (rtl) {
rtl_pos = Point2(size.width - revert_rect.position.x - (reload_icon->get_width() + padding + (1 * EDSCALE)), revert_rect.position.y);
}
Color color2(1, 1, 1);
if (revert_hover) {
color2.r *= 1.2;
color2.g *= 1.2;
color2.b *= 1.2;
Ref<StyleBox> sb_hover = get_theme_stylebox(SceneStringName(hover), "Button");
if (rtl) {
draw_style_box(sb_hover, Rect2(rtl_pos, revert_rect.size));
} else {
draw_style_box(sb_hover, revert_rect);
}
}
if (rtl) {
draw_texture(reload_icon, Vector2(size.width - revert_rect.position.x - reload_icon->get_width(), revert_rect.position.y), color2);
draw_texture(reload_icon, rtl_pos + Point2(padding, size.height - reload_icon->get_height()) / 2, color2);
} else {
draw_texture(reload_icon, revert_rect.position, color2);
draw_texture(reload_icon, revert_rect.position + Point2(padding, size.height - reload_icon->get_height()) / 2, color2);
}
} else {
revert_rect = Rect2();
@ -494,19 +509,32 @@ void EditorProperty::_notification(int p_what) {
key = get_editor_theme_icon(SNAME("Key"));
}
ofs -= key->get_width() + get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
ofs -= key->get_width() + half_padding + get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
keying_rect = Rect2(ofs, 0, key->get_width() + padding, size.height);
Point2 rtl_pos = Point2();
if (rtl) {
rtl_pos = Point2(size.width - keying_rect.position.x - (key->get_width() + padding), keying_rect.position.y);
}
Color color2(1, 1, 1);
if (keying_hover) {
color2.r *= 1.2;
color2.g *= 1.2;
color2.b *= 1.2;
Ref<StyleBox> sb_hover = get_theme_stylebox(SceneStringName(hover), "Button");
if (rtl) {
draw_style_box(sb_hover, Rect2(rtl_pos, keying_rect.size));
} else {
draw_style_box(sb_hover, keying_rect);
}
}
keying_rect = Rect2(ofs, ((size.height - key->get_height()) / 2), key->get_width(), key->get_height());
if (rtl) {
draw_texture(key, Vector2(size.width - keying_rect.position.x - key->get_width(), keying_rect.position.y), color2);
draw_texture(key, rtl_pos + Point2(padding, size.height - key->get_height()) / 2, color2);
} else {
draw_texture(key, keying_rect.position, color2);
draw_texture(key, keying_rect.position + Point2(padding, size.height - key->get_height()) / 2, color2);
}
} else {
@ -518,19 +546,32 @@ void EditorProperty::_notification(int p_what) {
close = get_editor_theme_icon(SNAME("Close"));
ofs -= close->get_width() + get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
ofs -= close->get_width() + half_padding + get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
delete_rect = Rect2(ofs, 0, close->get_width() + padding, size.height);
Point2 rtl_pos = Point2();
if (rtl) {
rtl_pos = Point2(size.width - delete_rect.position.x - (close->get_width() + padding), delete_rect.position.y);
}
Color color2(1, 1, 1);
if (delete_hover) {
color2.r *= 1.2;
color2.g *= 1.2;
color2.b *= 1.2;
Ref<StyleBox> sb_hover = get_theme_stylebox(SceneStringName(hover), "Button");
if (rtl) {
draw_style_box(sb_hover, Rect2(rtl_pos, delete_rect.size));
} else {
draw_style_box(sb_hover, delete_rect);
}
}
delete_rect = Rect2(ofs, ((size.height - close->get_height()) / 2), close->get_width(), close->get_height());
if (rtl) {
draw_texture(close, Vector2(size.width - delete_rect.position.x - close->get_width(), delete_rect.position.y), color2);
draw_texture(close, rtl_pos + Point2(padding, size.height - close->get_height()) / 2, color2);
} else {
draw_texture(close, delete_rect.position, color2);
draw_texture(close, delete_rect.position + Point2(padding, size.height - close->get_height()) / 2, color2);
}
} else {
delete_rect = Rect2();
@ -547,6 +588,15 @@ void EditorProperty::_notification(int p_what) {
get_parent()->disconnect(SceneStringName(theme_changed), callable_mp(this, &EditorProperty::_update_property_bg));
}
} break;
case NOTIFICATION_MOUSE_EXIT: {
if (keying_hover || revert_hover || check_hover || delete_hover) {
keying_hover = false;
revert_hover = false;
check_hover = false;
delete_hover = false;
queue_redraw();
}
} break;
}
}
@ -3782,13 +3832,16 @@ void EditorInspector::update_tree() {
ep->set_doc_path(doc_path);
ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL);
ep->update_property();
ep->_update_flags();
ep->update_editor_property_status();
ep->update_cache();
// If this property is favorited, it won't be in the tree yet. So don't do this setup right now.
if (ep->is_inside_tree()) {
ep->update_property();
ep->_update_flags();
ep->update_editor_property_status();
ep->update_cache();
if (current_selected && ep->property == current_selected) {
ep->select(current_focusable);
if (current_selected && ep->property == current_selected) {
ep->select(current_focusable);
}
}
}
}
@ -3841,6 +3894,16 @@ void EditorInspector::update_tree() {
for (EditorProperty *ep : KV2.value) {
vbox->add_child(ep);
// Now that it's inside the tree, do the setup.
ep->update_property();
ep->_update_flags();
ep->update_editor_property_status();
ep->update_cache();
if (current_selected && ep->property == current_selected) {
ep->select(current_focusable);
}
}
}
}
@ -4465,6 +4528,14 @@ void EditorInspector::_node_removed(Node *p_node) {
}
}
void EditorInspector::_gui_focus_changed(Control *p_control) {
if (!is_visible_in_tree() && !is_following_focus()) {
return;
}
// Don't follow focus when the inspector nor any of its children is focused. Prevents potential jumping when gaining focus.
set_follow_focus(has_focus() || child_has_focus());
}
void EditorInspector::_update_current_favorites() {
current_favorites.clear();
if (!can_favorite) {
@ -4662,6 +4733,10 @@ void EditorInspector::_notification(int p_what) {
if (!is_sub_inspector()) {
get_tree()->connect("node_removed", callable_mp(this, &EditorInspector::_node_removed));
}
Viewport *viewport = get_viewport();
ERR_FAIL_NULL(viewport);
viewport->connect("gui_focus_changed", callable_mp(this, &EditorInspector::_gui_focus_changed));
} break;
case NOTIFICATION_PREDELETE: {
@ -4744,15 +4819,6 @@ void EditorInspector::_notification(int p_what) {
update_tree();
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
set_follow_focus(true);
} break;
case NOTIFICATION_FOCUS_EXIT: {
// Don't follow focus when the inspector is not focused. Prevents potential jumping when gaining focus.
set_follow_focus(false);
} break;
}
}

View File

@ -606,6 +606,7 @@ class EditorInspector : public ScrollContainer {
void _clear_current_favorites();
void _node_removed(Node *p_node);
void _gui_focus_changed(Control *p_control);
HashMap<StringName, int> per_array_page;
void _page_change_request(int p_new_page, const StringName &p_array_prefix);

View File

@ -649,17 +649,11 @@ void EditorNode::_notification(int p_what) {
OS::get_singleton()->benchmark_begin_measure("Editor", "First Scan");
if (run_surface_upgrade_tool) {
run_surface_upgrade_tool = false;
SurfaceUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
SurfaceUpgradeTool::get_singleton()->finish_upgrade();
} else if (run_uid_upgrade_tool) {
run_uid_upgrade_tool = false;
UIDUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
UIDUpgradeTool::get_singleton()->finish_upgrade();
} else {
EditorFileSystem::get_singleton()->scan();
if (run_surface_upgrade_tool || run_uid_upgrade_tool) {
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorNode::_execute_upgrades), CONNECT_ONE_SHOT);
}
EditorFileSystem::get_singleton()->scan();
}
} break;
@ -901,6 +895,20 @@ void EditorNode::_update_update_spinner() {
OS::get_singleton()->set_low_processor_usage_mode(!update_continuously);
}
void EditorNode::_execute_upgrades() {
if (run_surface_upgrade_tool) {
run_surface_upgrade_tool = false;
// Execute another scan to reimport the modified files.
SurfaceUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
SurfaceUpgradeTool::get_singleton()->finish_upgrade();
} else if (run_uid_upgrade_tool) {
run_uid_upgrade_tool = false;
// Execute another scan to reimport the modified files.
UIDUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT);
UIDUpgradeTool::get_singleton()->finish_upgrade();
}
}
void EditorNode::init_plugins() {
_initializing_plugins = true;
Vector<String> addons;
@ -2049,45 +2057,7 @@ void EditorNode::try_autosave() {
}
void EditorNode::restart_editor(bool p_goto_project_manager) {
exiting = true;
if (project_run_bar->is_playing()) {
project_run_bar->stop_playing();
}
String to_reopen;
if (!p_goto_project_manager && get_tree()->get_edited_scene_root()) {
to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path();
}
_exit_editor(EXIT_SUCCESS);
List<String> args;
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
args.push_back(a);
}
if (p_goto_project_manager) {
args.push_back("--project-manager");
// Setup working directory.
const String exec_dir = OS::get_singleton()->get_executable_path().get_base_dir();
if (!exec_dir.is_empty()) {
args.push_back("--path");
args.push_back(exec_dir);
}
} else {
args.push_back("--path");
args.push_back(ProjectSettings::get_singleton()->get_resource_path());
args.push_back("-e");
}
if (!to_reopen.is_empty()) {
args.push_back(to_reopen);
}
OS::get_singleton()->set_restart_on_exit(true, args);
_menu_option_confirm(p_goto_project_manager ? PROJECT_QUIT_TO_PROJECT_MANAGER : PROJECT_RELOAD_CURRENT_PROJECT, false);
}
void EditorNode::_save_all_scenes() {
@ -3472,10 +3442,10 @@ void EditorNode::_discard_changes(const String &p_str) {
} break;
case PROJECT_QUIT_TO_PROJECT_MANAGER: {
restart_editor(true);
_restart_editor(true);
} break;
case PROJECT_RELOAD_CURRENT_PROJECT: {
restart_editor();
_restart_editor();
} break;
}
}
@ -4751,7 +4721,7 @@ Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) cons
const Node *node = Object::cast_to<const Node>(p_object);
if (node && node->has_meta(SceneStringName(_custom_type_script))) {
return node->get_meta(SceneStringName(_custom_type_script));
return PropertyUtils::get_custom_type_script(node);
}
Ref<Script> scr = p_object->get_script();
@ -5500,11 +5470,11 @@ bool EditorNode::has_scenes_in_session() {
}
void EditorNode::undo() {
trigger_menu_option(FILE_UNDO, true);
_menu_option_confirm(FILE_UNDO, true);
}
void EditorNode::redo() {
trigger_menu_option(FILE_REDO, true);
_menu_option_confirm(FILE_REDO, true);
}
bool EditorNode::ensure_main_scene(bool p_from_native) {
@ -5672,6 +5642,48 @@ bool EditorNode::_is_closing_editor() const {
return tab_closing_menu_option == FILE_QUIT || tab_closing_menu_option == PROJECT_QUIT_TO_PROJECT_MANAGER || tab_closing_menu_option == PROJECT_RELOAD_CURRENT_PROJECT;
}
void EditorNode::_restart_editor(bool p_goto_project_manager) {
exiting = true;
if (project_run_bar->is_playing()) {
project_run_bar->stop_playing();
}
String to_reopen;
if (!p_goto_project_manager && get_tree()->get_edited_scene_root()) {
to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path();
}
_exit_editor(EXIT_SUCCESS);
List<String> args;
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
args.push_back(a);
}
if (p_goto_project_manager) {
args.push_back("--project-manager");
// Setup working directory.
const String exec_dir = OS::get_singleton()->get_executable_path().get_base_dir();
if (!exec_dir.is_empty()) {
args.push_back("--path");
args.push_back(exec_dir);
}
} else {
args.push_back("--path");
args.push_back(ProjectSettings::get_singleton()->get_resource_path());
args.push_back("-e");
}
if (!to_reopen.is_empty()) {
args.push_back(to_reopen);
}
OS::get_singleton()->set_restart_on_exit(true, args);
}
void EditorNode::_scene_tab_closed(int p_tab) {
current_menu_option = SCENE_TAB_CLOSE;
tab_closing_idx = p_tab;

View File

@ -624,6 +624,7 @@ private:
void _proceed_closing_scene_tabs();
bool _is_closing_editor() const;
void _restart_editor(bool p_goto_project_manager = false);
Dictionary _get_main_scene_state();
void _set_main_scene_state(Dictionary p_state, Node *p_for_scene);
@ -675,6 +676,8 @@ private:
void _progress_dialog_visibility_changed();
void _load_error_dialog_visibility_changed();
void _execute_upgrades();
protected:
friend class FileSystemDock;

View File

@ -321,6 +321,9 @@ void EditorPropertyTextEnum::update_property() {
}
} else {
option_button->select(default_option);
if (default_option < 0) {
option_button->set_text(current_value);
}
}
}
@ -699,6 +702,7 @@ void EditorPropertyEnum::update_property() {
Variant current = get_edited_property_value();
if (current.get_type() == Variant::NIL) {
options->select(-1);
options->set_text("<null>");
return;
}
@ -709,6 +713,8 @@ void EditorPropertyEnum::update_property() {
return;
}
}
options->select(-1);
options->set_text(itos(which));
}
void EditorPropertyEnum::setup(const Vector<String> &p_options) {

View File

@ -1525,7 +1525,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
custom_list.append_array(export_plugins[i]->_get_export_features(Ref<EditorExportPlatform>(this), p_debug));
}
ProjectSettings::CustomMap custom_map;
ProjectSettings::CustomMap custom_map = get_custom_project_settings(p_preset);
if (path_remaps.size()) {
if (true) { //new remap mode, use always as it's friendlier with multiple .pck exports
for (int i = 0; i < path_remaps.size(); i += 2) {

View File

@ -336,6 +336,7 @@ public:
virtual void get_platform_features(List<String> *r_features) const = 0;
virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) {}
virtual String get_debug_protocol() const { return "tcp://"; }
virtual HashMap<String, Variant> get_custom_project_settings(const Ref<EditorExportPreset> &p_preset) const { return HashMap<String, Variant>(); }
EditorExportPlatform();
};

View File

@ -1573,8 +1573,7 @@ void FileSystemDock::_update_resource_paths_after_move(const HashMap<String, Str
}
}
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
EditorNode::get_editor_data().script_class_save_global_classes();
EditorFileSystem::get_singleton()->emit_signal(SNAME("script_classes_updated"));
}
@ -1708,8 +1707,7 @@ void FileSystemDock::_resource_removed(const Ref<Resource> &p_resource) {
const Ref<Script> &scr = p_resource;
if (scr.is_valid()) {
ScriptServer::remove_global_class_by_path(scr->get_path());
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
EditorNode::get_editor_data().script_class_save_global_classes();
EditorFileSystem::get_singleton()->emit_signal(SNAME("script_classes_updated"));
}
emit_signal(SNAME("resource_removed"), p_resource);

View File

@ -127,8 +127,26 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
emit_signal(SNAME("files_selected"), files);
} else {
if (mode == FILE_MODE_SAVE_FILE) {
if (p_filter != 0 && p_filter != filter->get_item_count() - 1) {
bool valid = false;
bool valid = false;
if (p_filter == filter->get_item_count() - 1) {
valid = true; // Match none.
} else if (filters.size() > 1 && p_filter == 0) {
// Match all filters.
for (int i = 0; i < filters.size(); i++) {
String flt = filters[i].get_slice(";", 0);
for (int j = 0; j < flt.get_slice_count(","); j++) {
String str = flt.get_slice(",", j).strip_edges();
if (f.matchn(str)) {
valid = true;
break;
}
}
if (valid) {
break;
}
}
} else {
int idx = p_filter;
if (filters.size() > 1) {
idx--;
@ -138,7 +156,7 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
String str = (flt.get_slice(",", j).strip_edges());
if (f.match(str)) {
if (f.matchn(str)) {
valid = true;
break;
}
@ -147,9 +165,21 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
if (!valid && filter_slice_count > 0) {
String str = (flt.get_slice(",", 0).strip_edges());
f += str.substr(1, str.length() - 1);
file->set_text(f.get_file());
valid = true;
}
} else {
valid = true;
}
}
// Add first extension of filter if no valid extension is found.
if (!valid) {
int idx = p_filter;
String flt = filters[idx].get_slice(";", 0);
String ext = flt.get_slice(",", 0).strip_edges().get_extension();
f += "." + ext;
}
emit_signal(SNAME("file_selected"), f);
} else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) {
emit_signal(SNAME("file_selected"), f);
@ -578,9 +608,9 @@ void EditorFileDialog::_action_pressed() {
bool valid = false;
if (filter->get_selected() == filter->get_item_count() - 1) {
valid = true; // match none
valid = true; // Match none.
} else if (filters.size() > 1 && filter->get_selected() == 0) {
// match all filters
// Match all filters.
for (int i = 0; i < filters.size(); i++) {
String flt = filters[i].get_slice(";", 0);
for (int j = 0; j < flt.get_slice_count(","); j++) {

View File

@ -924,12 +924,19 @@ void SceneTreeEditor::_update_tree(bool p_scroll_to_selected) {
// If pinned state changed, update the currently pinned node.
if (AnimationPlayerEditor::get_singleton()->is_pinned() != node_cache.current_has_pin) {
node_cache.current_has_pin = AnimationPlayerEditor::get_singleton()->is_pinned();
node_cache.mark_dirty(pinned_node);
if (node_cache.has(pinned_node)) {
node_cache.mark_dirty(pinned_node);
}
}
// If the current pinned node changed update both the old and new node.
if (node_cache.current_pinned_node != pinned_node) {
node_cache.mark_dirty(pinned_node);
node_cache.mark_dirty(node_cache.current_pinned_node);
// get_editing_node() will return deleted nodes. If the nodes are not in cache don't try to mark them.
if (node_cache.has(pinned_node)) {
node_cache.mark_dirty(pinned_node);
}
if (node_cache.has(node_cache.current_pinned_node)) {
node_cache.mark_dirty(node_cache.current_pinned_node);
}
node_cache.current_pinned_node = pinned_node;
}
@ -2373,6 +2380,10 @@ HashMap<Node *, SceneTreeEditor::CachedNode>::Iterator SceneTreeEditor::NodeCach
return I;
}
bool SceneTreeEditor::NodeCache::has(Node *p_node) {
return get(p_node, false).operator bool();
}
void SceneTreeEditor::NodeCache::remove(Node *p_node, bool p_recursive) {
if (!p_node) {
return;
@ -2382,6 +2393,11 @@ void SceneTreeEditor::NodeCache::remove(Node *p_node, bool p_recursive) {
editor->selected = nullptr;
}
if (p_node == current_pinned_node) {
current_pinned_node = nullptr;
current_has_pin = false;
}
editor->marked.erase(p_node);
HashMap<Node *, CachedNode>::Iterator I = cache.find(p_node);
@ -2419,6 +2435,7 @@ void SceneTreeEditor::NodeCache::mark_dirty(Node *p_node, bool p_parents) {
if (!p_parents) {
break;
}
node = node->get_parent();
}
}
@ -2483,4 +2500,6 @@ void SceneTreeEditor::NodeCache::clear() {
}
cache.clear();
to_delete.clear();
current_pinned_node = nullptr;
current_has_pin = false;
}

View File

@ -90,6 +90,7 @@ class SceneTreeEditor : public Control {
HashMap<Node *, CachedNode>::Iterator add(Node *p_node, TreeItem *p_item);
HashMap<Node *, CachedNode>::Iterator get(Node *p_node, bool p_deleted_ok = true);
bool has(Node *p_node);
void remove(Node *p_node, bool p_recursive = false);
void mark_dirty(Node *p_node, bool p_parents = true);
void mark_children_dirty(Node *p_node, bool p_recursive = false);

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#eee" d="M6 2a4 4 0 0 0-2 7.453V13a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V9.453A4 4 0 0 0 10 2zM5 5a1 1 0 0 1 0 2 1 1 0 0 1 0-2zm6 0a1 1 0 0 1 0 2 1 1 0 0 1 0-2zM7 7h2v1H7zM5 9h1v1h1V9h2v1h1V9h1v4h-1v-1H9v1H7v-1H6v1H5V8z"/></svg>

After

Width:  |  Height:  |  Size: 295 B

View File

@ -1344,7 +1344,7 @@ void SceneImportSettingsDialog::_notification(int p_what) {
light_2_switch->set_button_icon(theme_cache.light_2_icon);
light_rotate_switch->set_button_icon(theme_cache.rotate_icon);
animation_toggle_skeleton_visibility->set_button_icon(get_editor_theme_icon(SNAME("Skeleton3D")));
animation_toggle_skeleton_visibility->set_button_icon(get_editor_theme_icon(SNAME("SkeletonPreview")));
} break;
case NOTIFICATION_PROCESS: {
@ -1740,7 +1740,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
animation_toggle_skeleton_visibility = memnew(Button);
animation_hbox->add_child(animation_toggle_skeleton_visibility);
animation_toggle_skeleton_visibility->set_toggle_mode(true);
animation_toggle_skeleton_visibility->set_flat(true);
animation_toggle_skeleton_visibility->set_theme_type_variation("FlatButton");
animation_toggle_skeleton_visibility->set_focus_mode(Control::FOCUS_NONE);
animation_toggle_skeleton_visibility->set_tooltip_text(TTR("Toggle Animation Skeleton Visibility"));

View File

@ -100,6 +100,13 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
if (atlas->is_compressed()) {
atlas = atlas->duplicate();
if (atlas->decompress() != OK) {
return Ref<Texture2D>();
}
}
if (!tex_atlas->get_region().has_area()) {
return Ref<Texture2D>();
}

View File

@ -73,20 +73,10 @@ void EmbeddedProcess::_notification(int p_what) {
} break;
case NOTIFICATION_APPLICATION_FOCUS_IN: {
application_has_focus = true;
if (embedded_process_was_focused) {
embedded_process_was_focused = false;
// Refocus the embedded process if it was focused when the application lost focus,
// but do not refocus if the embedded process is currently focused (indicating it just lost focus)
// or if the current window is a different popup or secondary window.
if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) {
grab_focus();
queue_update_embedded_process();
}
}
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
application_has_focus = false;
embedded_process_was_focused = embedding_completed && current_process_id == focused_process_id;
} break;
}
}
@ -295,14 +285,27 @@ void EmbeddedProcess::_check_mouse_over() {
// This method checks if the mouse is over the embedded process while the current application is focused.
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
// allowing the user to interact with it immediately without needing to click first.
if (!is_visible_in_tree() || !embedding_completed || !application_has_focus || !window || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
if (!embedding_completed || !application_has_focus || !window || has_focus() || !is_visible_in_tree() || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
return;
}
bool focused = has_focus();
// Before checking whether the mouse is truly inside the embedded process, ensure
// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
// `_check_mouse_over` may be triggered before the editor hides the game workspace.
// This prevents the embedded process from regaining focus immediately after the editor has taken it.
if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
return;
}
// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
// while the floating game window is being resized.
BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
if (!mouse_button_mask.is_empty()) {
return;
}
// Not stealing focus from a textfield.
if (!focused && get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
return;
}
@ -318,14 +321,17 @@ void EmbeddedProcess::_check_mouse_over() {
return;
}
// When we already have the focus and the user moves the mouse over the embedded process,
// we just need to refocus the process.
if (focused) {
queue_update_embedded_process();
} else {
grab_focus();
queue_redraw();
// When there's a modal window, we don't want to grab the focus to prevent
// the game window to go in front of the modal window.
if (_get_current_modal_window()) {
return;
}
// Force "regrabbing" the game window focus.
last_updated_embedded_process_focused = false;
grab_focus();
queue_redraw();
}
void EmbeddedProcess::_check_focused_process_id() {
@ -334,17 +340,48 @@ void EmbeddedProcess::_check_focused_process_id() {
focused_process_id = process_id;
if (focused_process_id == current_process_id) {
// The embedded process got the focus.
emit_signal(SNAME("embedded_process_focused"));
if (has_focus()) {
// Redraw to updated the focus style.
queue_redraw();
} else {
grab_focus();
// Refocus the current model when focusing the embedded process.
Window *modal_window = _get_current_modal_window();
if (!modal_window) {
emit_signal(SNAME("embedded_process_focused"));
if (has_focus()) {
// Redraw to updated the focus style.
queue_redraw();
} else {
grab_focus();
}
}
} else if (has_focus()) {
release_focus();
}
}
// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
if (!application_has_focus && focused_process_id == current_process_id) {
Window *modal_window = _get_current_modal_window();
if (modal_window) {
if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
modal_window->set_mode(Window::MODE_WINDOWED);
}
callable_mp(modal_window, &Window::grab_focus).call_deferred();
}
}
}
Window *EmbeddedProcess::_get_current_modal_window() {
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
for (const DisplayServer::WindowID &window_id : wl) {
Window *w = Window::get_from_id(window_id);
if (!w) {
continue;
}
if (w->is_exclusive()) {
return w;
}
}
return nullptr;
}
void EmbeddedProcess::_bind_methods() {

View File

@ -37,7 +37,7 @@ class EmbeddedProcess : public Control {
GDCLASS(EmbeddedProcess, Control);
bool application_has_focus = true;
bool embedded_process_was_focused = false;
uint64_t last_application_focus_time = 0;
OS::ProcessID focused_process_id = 0;
OS::ProcessID current_process_id = 0;
bool embedding_grab_focus = false;
@ -68,6 +68,7 @@ class EmbeddedProcess : public Control {
void _check_focused_process_id();
bool _is_embedded_process_updatable();
Rect2i _get_global_embedded_window_rect();
Window *_get_current_modal_window();
protected:
static void _bind_methods();

View File

@ -64,6 +64,7 @@ void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
settings["editors/panning/warped_mouse_panning"] = EDITOR_GET("editors/panning/warped_mouse_panning");
settings["editors/panning/2d_editor_pan_speed"] = EDITOR_GET("editors/panning/2d_editor_pan_speed");
settings["canvas_item_editor/pan_view"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("canvas_item_editor/pan_view"));
settings["editors/3d/freelook/freelook_base_speed"] = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
setup_data.append(settings);
p_session->send_message("scene:runtime_node_select_setup", setup_data);
@ -243,20 +244,29 @@ void GameView::_show_update_window_wrapper() {
Size2i size = floating_window_rect.size;
int screen = floating_window_screen;
Size2 wrapped_margins_size = window_wrapper->get_margins_size();
Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position();
offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT);
offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP);
// Obtain the size around the embedded process control. Usually, the difference between the game view's get_size
// and the embedded control should work. However, when the control is hidden and has never been displayed,
// the size of the embedded control is not calculated.
Size2 old_min_size = embedded_process->get_custom_minimum_size();
embedded_process->set_custom_minimum_size(Size2i());
Size2 min_size = get_minimum_size();
Size2 embedded_process_min_size = get_minimum_size();
Size2 wrapped_margins_size = window_wrapper->get_margins_size();
Size2 wrapped_min_size = window_wrapper->get_minimum_size();
Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position();
// On the first startup, the global position of the embedded process control is invalid because it was
// never displayed. We will calculated it manually using the minimum size of the window.
if (offset_embedded_process == Point2()) {
offset_embedded_process.y = wrapped_min_size.y;
}
offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT);
offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP);
offset_embedded_process += window_wrapper->get_margins_top_left();
embedded_process->set_custom_minimum_size(old_min_size);
Point2 size_diff_embedded_process = Point2(0, min_size.y) + embedded_process->get_margins_size();
Point2 size_diff_embedded_process = Point2(0, embedded_process_min_size.y) + embedded_process->get_margins_size();
if (placement.position != Point2i(INT_MAX, INT_MAX)) {
position = placement.position - offset_embedded_process;
@ -436,6 +446,10 @@ GameView::EmbedAvailability GameView::_get_embed_available() {
if (get_tree()->get_root()->is_embedding_subwindows()) {
return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE;
}
String display_driver = GLOBAL_GET("display/display_server/driver");
if (display_driver == "headless" || display_driver == "wayland") {
return EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER;
}
EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
if (placement.force_fullscreen) {
@ -479,7 +493,14 @@ void GameView::_update_ui() {
}
break;
case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED:
state_label->set_text(TTR("Game embedding not available on your OS."));
if (DisplayServer::get_singleton()->get_name() == "Wayland") {
state_label->set_text(TTR("Game embedding not available on Wayland.\nWayland can be disabled in the Editor Settings (Run > Platforms > Linux/*BSD > Prefer Wayland)."));
} else {
state_label->set_text(TTR("Game embedding not available on your OS."));
}
break;
case EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER:
state_label->set_text(vformat(TTR("Game embedding not available for the Display Server: '%s'.\nDisplay Server can be modified in the Project Settings (Display > Display Server > Driver)."), GLOBAL_GET("display/display_server/driver")));
break;
case EMBED_NOT_AVAILABLE_MINIMIZED:
state_label->set_text(TTR("Game embedding not available when the game starts minimized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact."));
@ -786,8 +807,21 @@ void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_argumen
// Be sure to have the correct window size in the embedded_process control.
_update_embed_window_size();
Rect2i rect = embedded_process->get_screen_embedded_window_rect();
// When using the floating window, we need to force the position and size from the
// editor/project settings, because the get_screen_embedded_window_rect of the
// embedded_process will be updated only on the next frame.
if (p_idx == 0 && window_wrapper->get_window_enabled()) {
EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
if (placement.position != Point2i(INT_MAX, INT_MAX)) {
rect.position = placement.position;
}
if (placement.size != Size2i()) {
rect.size = placement.size;
}
}
N = r_arguments.insert_after(N, "--position");
N = r_arguments.insert_after(N, itos(rect.position.x) + "," + itos(rect.position.y));
N = r_arguments.insert_after(N, "--resolution");

View File

@ -108,6 +108,7 @@ class GameView : public VBoxContainer {
EMBED_NOT_AVAILABLE_MAXIMIZED,
EMBED_NOT_AVAILABLE_FULLSCREEN,
EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE,
EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER,
};
inline static GameView *singleton = nullptr;

View File

@ -4382,8 +4382,11 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node,
void _insert_rid_recursive(Node *node, HashSet<RID> &rids) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node);
if (co) {
rids.insert(co->get_rid());
} else if (node->is_class("CSGShape3D")) { // HACK: We should avoid referencing module logic.
rids.insert(node->call("_get_root_collision_instance"));
}
for (int i = 0; i < node->get_child_count(); i++) {

View File

@ -535,12 +535,14 @@ Polygon3DEditor::Polygon3DEditor() {
button_create = memnew(Button);
button_create->set_theme_type_variation(SceneStringName(FlatButton));
button_create->set_tooltip_text(TTRC("Create Polygon"));
add_child(button_create);
button_create->connect(SceneStringName(pressed), callable_mp(this, &Polygon3DEditor::_menu_option).bind(MODE_CREATE));
button_create->set_toggle_mode(true);
button_edit = memnew(Button);
button_edit->set_theme_type_variation(SceneStringName(FlatButton));
button_edit->set_tooltip_text(TTRC("Edit Polygon"));
add_child(button_edit);
button_edit->connect(SceneStringName(pressed), callable_mp(this, &Polygon3DEditor::_menu_option).bind(MODE_EDIT));
button_edit->set_toggle_mode(true);

View File

@ -2843,10 +2843,13 @@ void ScriptEditor::_reload_scripts(bool p_refresh_only) {
Ref<Resource> edited_res = se->get_edited_resource();
if (edited_res->is_built_in()) {
continue; //internal script, who cares
continue; // Internal script, who cares.
}
if (!p_refresh_only) {
if (p_refresh_only) {
// Make sure the modified time is correct.
se->edited_file_data.last_modified_time = FileAccess::get_modified_time(edited_res->get_path());
} else {
uint64_t last_date = se->edited_file_data.last_modified_time;
uint64_t date = FileAccess::get_modified_time(edited_res->get_path());

View File

@ -833,6 +833,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
left_panel->set_custom_minimum_size(Size2(100, 300) * EDSCALE);
shader_tabs = memnew(TabContainer);
shader_tabs->set_custom_minimum_size(Size2(460, 300) * EDSCALE);
shader_tabs->set_tabs_visible(false);
shader_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
main_split->add_child(shader_tabs);

View File

@ -132,8 +132,21 @@ static Image::Format get_texture_2d_format(const Ref<Texture2D> &p_texture) {
static int get_texture_mipmaps_count(const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND_V(p_texture.is_null(), -1);
// We are having to download the image only to get its mipmaps count. It would be nice if we didn't have to.
Ref<Image> image = p_texture->get_image();
Ref<Image> image;
Ref<AtlasTexture> at = p_texture;
if (at.is_valid()) {
// The AtlasTexture tries to obtain the region from the atlas as an image,
// which will fail if it is a compressed format.
Ref<Texture2D> atlas = at->get_atlas();
if (atlas.is_valid()) {
image = atlas->get_image();
}
} else {
image = p_texture->get_image();
}
if (image.is_valid()) {
return image->get_mipmap_count();
}

View File

@ -971,40 +971,44 @@ void TileSetAtlasSourceEditor::_update_atlas_view() {
tile_create_help->set_visible(tools_button_group->get_pressed_button() == tool_setup_atlas_source_button);
}
Vector2i pos;
Vector2 texture_region_base_size = tile_set_atlas_source->get_texture_region_size();
int texture_region_base_size_min = MIN(texture_region_base_size.x, texture_region_base_size.y);
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
int alternative_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
if (alternative_count > 1) {
// Compute the right extremity of alternative.
int y_increment = 0;
pos.x = 0;
for (int j = 1; j < alternative_count; j++) {
int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
Rect2i rect = tile_atlas_view->get_alternative_tile_rect(tile_id, alternative_id);
pos.x = MAX(pos.x, rect.get_end().x);
y_increment = MAX(y_increment, rect.size.y);
if (tools_button_group->get_pressed_button() != tool_paint_button) {
Vector2i pos;
Vector2 texture_region_base_size = tile_set_atlas_source->get_texture_region_size();
int texture_region_base_size_min = MIN(texture_region_base_size.x, texture_region_base_size.y);
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
int alternative_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
if (alternative_count > 1) {
// Compute the right extremity of alternative.
int y_increment = 0;
pos.x = 0;
for (int j = 1; j < alternative_count; j++) {
int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
Rect2i rect = tile_atlas_view->get_alternative_tile_rect(tile_id, alternative_id);
pos.x = MAX(pos.x, rect.get_end().x);
y_increment = MAX(y_increment, rect.size.y);
}
// Create and position the button.
Button *button = memnew(Button);
button->set_flat(true);
button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
button->add_theme_style_override(CoreStringName(normal), memnew(StyleBoxEmpty));
button->add_theme_style_override(SceneStringName(hover), memnew(StyleBoxEmpty));
button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
button->add_theme_style_override(SceneStringName(pressed), memnew(StyleBoxEmpty));
button->add_theme_constant_override("align_to_largest_stylebox", false);
button->set_mouse_filter(Control::MOUSE_FILTER_PASS);
button->connect(SceneStringName(pressed), callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_create_button_pressed).bind(tile_id));
button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min)));
button->set_expand_icon(true);
alternative_tiles_control->add_child(button);
pos.y += y_increment;
}
// Create and position the button.
Button *button = memnew(Button);
button->set_flat(true);
button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
button->add_theme_style_override(CoreStringName(normal), memnew(StyleBoxEmpty));
button->add_theme_style_override(SceneStringName(hover), memnew(StyleBoxEmpty));
button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
button->add_theme_style_override(SceneStringName(pressed), memnew(StyleBoxEmpty));
button->connect(SceneStringName(pressed), callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile).bind(tile_id, TileSetSource::INVALID_TILE_ALTERNATIVE));
button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min)));
button->set_expand_icon(true);
alternative_tiles_control->add_child(button);
pos.y += y_increment;
}
tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min);
}
tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min);
// Redraw everything.
tile_atlas_control->queue_redraw();
@ -2009,6 +2013,17 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() {
alternative_tiles_control_unscaled->queue_redraw();
}
void TileSetAtlasSourceEditor::_tile_alternatives_create_button_pressed(const Vector2i &p_atlas_coords) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
// FIXME: Doesn't undo changes to `next_alternative_id` counter.
undo_redo->create_action(TTR("Create tile alternatives"));
int next_id = tile_set_atlas_source->get_next_alternative_tile_id(p_atlas_coords);
undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", p_atlas_coords, next_id);
undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", p_atlas_coords, next_id);
undo_redo->commit_action();
}
void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() {
// Update the hovered alternative tile.
if (tools_button_group->get_pressed_button() == tool_select_button) {

View File

@ -253,6 +253,7 @@ private:
PopupMenu *alternative_tile_popup_menu = nullptr;
Control *alternative_tiles_control = nullptr;
Control *alternative_tiles_control_unscaled = nullptr;
void _tile_alternatives_create_button_pressed(const Vector2i &p_atlas_coords);
void _tile_alternatives_control_draw();
void _tile_alternatives_control_unscaled_draw();
void _tile_alternatives_control_mouse_exited();

View File

@ -2834,7 +2834,7 @@ void SceneTreeDock::_update_script_button() {
Ref<Script> cts;
if (n->has_meta(SceneStringName(_custom_type_script))) {
cts = n->get_meta(SceneStringName(_custom_type_script));
cts = PropertyUtils::get_custom_type_script(n);
}
if (selection.size() == 1) {
@ -3114,7 +3114,7 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro
// If we're dealing with a custom node type, we need to create a default instance of the custom type instead of the native type for property comparison.
if (oldnode->has_meta(SceneStringName(_custom_type_script))) {
Ref<Script> cts = oldnode->get_meta(SceneStringName(_custom_type_script));
Ref<Script> cts = PropertyUtils::get_custom_type_script(oldnode);
default_oldnode = Object::cast_to<Node>(get_editor_data()->script_class_instance(cts->get_global_name()));
if (default_oldnode) {
default_oldnode->set_name(cts->get_global_name());
@ -3618,7 +3618,7 @@ void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) {
} else {
// Check if dropped script is compatible.
if (n->has_meta(SceneStringName(_custom_type_script))) {
Ref<Script> ct_scr = n->get_meta(SceneStringName(_custom_type_script));
Ref<Script> ct_scr = PropertyUtils::get_custom_type_script(n);
if (!scr->inherits_script(ct_scr)) {
String custom_type_name = ct_scr->get_global_name();

View File

@ -338,6 +338,14 @@ Size2 WindowWrapper::get_margins_size() {
return Size2(margins->get_margin_size(SIDE_LEFT) + margins->get_margin_size(SIDE_RIGHT), margins->get_margin_size(SIDE_TOP) + margins->get_margin_size(SIDE_RIGHT));
}
Size2 WindowWrapper::get_margins_top_left() {
if (!margins) {
return Size2();
}
return Size2(margins->get_margin_size(SIDE_LEFT), margins->get_margin_size(SIDE_TOP));
}
void WindowWrapper::grab_window_focus() {
if (get_window_enabled() && is_visible()) {
window->grab_focus();

View File

@ -86,6 +86,7 @@ public:
void set_window_title(const String &p_title);
void set_margins_enabled(bool p_enabled);
Size2 get_margins_size();
Size2 get_margins_top_left();
void grab_window_focus();
void set_override_close_request(bool p_enabled);

View File

@ -864,6 +864,11 @@ void Main::test_cleanup() {
int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) {
for (int x = 0; x < argc; x++) {
// Early return to ignore a possible user-provided "--test" argument.
if ((strlen(argv[x]) == 2) && ((strncmp(argv[x], "--", 2) == 0) || (strncmp(argv[x], "++", 2) == 0))) {
tests_need_run = false;
return EXIT_SUCCESS;
}
if ((strncmp(argv[x], "--test", 6) == 0) && (strlen(argv[x]) == 6)) {
tests_need_run = true;
#ifdef TESTS_ENABLED
@ -3116,7 +3121,7 @@ Error Main::setup2(bool p_show_boot_logo) {
OS::get_singleton()->benchmark_begin_measure("Servers", "Tablet Driver");
GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver", "");
GLOBAL_DEF_RST_NOVAL(PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.windows", PROPERTY_HINT_ENUM, "winink,wintab,dummy"), "");
GLOBAL_DEF_RST_NOVAL(PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.windows", PROPERTY_HINT_ENUM, "auto,winink,wintab,dummy"), "");
if (tablet_driver.is_empty()) { // specified in project.godot
tablet_driver = GLOBAL_GET("input_devices/pen_tablet/driver");
@ -3136,7 +3141,7 @@ Error Main::setup2(bool p_show_boot_logo) {
DisplayServer::get_singleton()->tablet_set_current_driver(DisplayServer::get_singleton()->tablet_get_driver_name(0));
}
print_verbose("Using \"" + tablet_driver + "\" pen tablet driver...");
print_verbose("Using \"" + DisplayServer::get_singleton()->tablet_get_current_driver() + "\" pen tablet driver...");
OS::get_singleton()->benchmark_end_measure("Servers", "Tablet Driver");
}

View File

@ -294,13 +294,16 @@ Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/set_table_c
Added optional "shrink" argument. Compatibility method registered.
GH-100062
GH-101561
--------
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/index_buffer_create/arguments': size changed value in new API, from 4 to 5.
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/uniform_buffer_create/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/storage_buffer_create/arguments': size changed value in new API, from 3 to 4.
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/vertex_buffer_create/arguments': size changed value in new API, from 3 to 4.
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/vertex_buffer_create/arguments/2': default_value changed value in new API, from "false" to "0".
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/vertex_buffer_create/arguments/2': type changed value in new API, from "bool" to "bitfield::RenderingDevice.BufferCreationBits".
Optional argument added. Compatibility methods registered.
Optional argument (creation flags) added. Compatibility methods registered.
GH-101531
@ -316,3 +319,10 @@ GH-100913
Validate extension JSON: Error: Field 'classes/TextEdit/methods/get_line_column_at_pos/arguments': size changed value in new API, from 2 to 3.
Added optional argument to disallow positions that are outside the column range of the line. Compatibility method registered.
GH-102796
---------
Validate extension JSON: Error: Field 'classes/GraphEdit/signals/frame_rect_changed/arguments/1': type changed value in new API, from "Vector2" to "Rect2".
Previous type was incorrect. No compatibility system for signal arguments.

View File

@ -166,6 +166,16 @@ bool CSGShape3D::get_collision_mask_value(int p_layer_number) const {
return get_collision_mask() & (1 << (p_layer_number - 1));
}
RID CSGShape3D::_get_root_collision_instance() const {
if (root_collision_instance.is_valid()) {
return root_collision_instance;
} else if (parent_shape) {
return parent_shape->_get_root_collision_instance();
}
return RID();
}
void CSGShape3D::set_collision_priority(real_t p_priority) {
collision_priority = p_priority;
if (root_collision_instance.is_valid()) {
@ -982,6 +992,8 @@ void CSGShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CSGShape3D::set_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CSGShape3D::get_collision_mask_value);
ClassDB::bind_method(D_METHOD("_get_root_collision_instance"), &CSGShape3D::_get_root_collision_instance);
ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CSGShape3D::set_collision_layer_value);
ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CSGShape3D::get_collision_layer_value);

View File

@ -156,6 +156,8 @@ public:
void set_collision_mask_value(int p_layer_number, bool p_value);
bool get_collision_mask_value(int p_layer_number) const;
RID _get_root_collision_instance() const;
void set_collision_priority(real_t p_priority);
real_t get_collision_priority() const;

View File

@ -301,6 +301,7 @@ void ENetMultiplayerPeer::close() {
}
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
E.value->flush();
E.value->destroy();
}
active_mode = MODE_NONE;

View File

@ -878,8 +878,7 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap<St
if (requires_type && !ClassDB::is_parent_class(p_dir->get_file_type(i), p_required_type)) {
continue;
}
ScriptLanguage::CodeCompletionOption option(p_dir->get_file_path(i), ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH);
option.insert_text = option.display.quote(quote_style);
ScriptLanguage::CodeCompletionOption option(p_dir->get_file_path(i).quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH);
r_list.insert(option.display, option);
}

View File

@ -3487,6 +3487,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
if (preload->path == nullptr) {
push_error(R"(Expected resource path after "(".)");
} else if (preload->path->type == Node::LITERAL) {
override_completion_context(preload->path, COMPLETION_RESOURCE_PATH, preload);
}
pop_completion_call();

View File

@ -1002,7 +1002,8 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
// We denoise in fixed size regions and synchronize execution to avoid GPU timeouts.
// We use a region with 1/4 the amount of pixels if we're denoising SH lightmaps, as
// all four of them are denoised in the shader in one dispatch.
const int max_region_size = p_bake_sh ? 512 : 1024;
const int user_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size")));
const int max_region_size = p_bake_sh ? user_region_size / 2 : user_region_size;
int x_regions = Math::division_round_up(p_atlas_size.width, max_region_size);
int y_regions = Math::division_round_up(p_atlas_size.height, max_region_size);
for (int s = 0; s < p_atlas_slices; s++) {

View File

@ -35,6 +35,8 @@
#include "packet_peer_mbed_dtls.h"
#include "stream_peer_mbedtls.h"
#include "core/config/project_settings.h"
#if MBEDTLS_VERSION_MAJOR >= 3
#include <psa/crypto.h>
#endif
@ -50,6 +52,8 @@ void initialize_mbedtls_module(ModuleInitializationLevel p_level) {
return;
}
GLOBAL_DEF("network/tls/enable_tls_v1.3", false);
#if MBEDTLS_VERSION_MAJOR >= 3
int status = psa_crypto_init();
ERR_FAIL_COND_MSG(status != PSA_SUCCESS, "Failed to initialize psa crypto. The mbedTLS modules will not work.");

View File

@ -30,6 +30,8 @@
#include "tls_context_mbedtls.h"
#include "core/config/project_settings.h"
static void my_debug(void *ctx, int level,
const char *file, int line,
const char *str) {
@ -144,6 +146,11 @@ Error TLSContextMbedTLS::init_server(int p_transport, Ref<TLSOptions> p_options,
cookies = p_cookies;
mbedtls_ssl_conf_dtls_cookies(&conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, &(cookies->cookie_ctx));
}
if (Engine::get_singleton()->is_editor_hint() || !(bool)GLOBAL_GET("network/tls/enable_tls_v1.3")) {
mbedtls_ssl_conf_max_tls_version(&conf, MBEDTLS_SSL_VERSION_TLS1_2);
}
mbedtls_ssl_setup(&tls, &conf);
return OK;
}
@ -187,6 +194,10 @@ Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname,
}
}
if (Engine::get_singleton()->is_editor_hint() || !(bool)GLOBAL_GET("network/tls/enable_tls_v1.3")) {
mbedtls_ssl_conf_max_tls_version(&conf, MBEDTLS_SSL_VERSION_TLS1_2);
}
// Set valid CAs
mbedtls_ssl_conf_ca_chain(&conf, &(cas->cert), nullptr);
mbedtls_ssl_setup(&tls, &conf);

View File

@ -101,4 +101,13 @@ public class ExportDiagnosticsTests
new string[] { "ExportDiagnostics_GD0110_ScriptProperties.generated.cs" }
);
}
[Fact]
public async void ExportToolButtonStoringCallable()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "ExportDiagnostics_GD0111.cs" },
new string[] { "ExportDiagnostics_GD0111_ScriptProperties.generated.cs" }
);
}
}

View File

@ -9,22 +9,12 @@ partial class ExportDiagnostics_GD0108
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButton' field.
/// Cached name for the 'MyButton' property.
/// </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) {

View File

@ -9,22 +9,12 @@ partial class ExportDiagnostics_GD0109
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButton' field.
/// Cached name for the 'MyButton' property.
/// </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) {

View File

@ -9,26 +9,16 @@ partial class ExportDiagnostics_GD0110
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButton' field.
/// Cached name for the 'MyButton' property.
/// </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);
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@MyButton);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);

View File

@ -0,0 +1,116 @@
using Godot;
using Godot.NativeInterop;
partial class ExportDiagnostics_GD0111
{
#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 'MyButtonGet' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGet = "MyButtonGet";
/// <summary>
/// Cached name for the 'MyButtonGetSet' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGetSet = "MyButtonGetSet";
/// <summary>
/// Cached name for the 'MyButtonGetWithBackingField' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGetWithBackingField = "MyButtonGetWithBackingField";
/// <summary>
/// Cached name for the 'MyButtonGetSetWithBackingField' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGetSetWithBackingField = "MyButtonGetSetWithBackingField";
/// <summary>
/// Cached name for the 'MyButtonOkWithCallableCreationExpression' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonOkWithCallableCreationExpression = "MyButtonOkWithCallableCreationExpression";
/// <summary>
/// Cached name for the 'MyButtonOkWithImplicitCallableCreationExpression' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonOkWithImplicitCallableCreationExpression = "MyButtonOkWithImplicitCallableCreationExpression";
/// <summary>
/// Cached name for the 'MyButtonOkWithCallableFromExpression' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonOkWithCallableFromExpression = "MyButtonOkWithCallableFromExpression";
/// <summary>
/// Cached name for the '_backingField' field.
/// </summary>
public new static readonly global::Godot.StringName @_backingField = "_backingField";
}
/// <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.@MyButtonGetSet) {
this.@MyButtonGetSet = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
return true;
}
if (name == PropertyName.@MyButtonGetSetWithBackingField) {
this.@MyButtonGetSetWithBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
return true;
}
if (name == PropertyName.@_backingField) {
this.@_backingField = 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.@MyButtonGet) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGet);
return true;
}
if (name == PropertyName.@MyButtonGetSet) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGetSet);
return true;
}
if (name == PropertyName.@MyButtonGetWithBackingField) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGetWithBackingField);
return true;
}
if (name == PropertyName.@MyButtonGetSetWithBackingField) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGetSetWithBackingField);
return true;
}
if (name == PropertyName.@MyButtonOkWithCallableCreationExpression) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonOkWithCallableCreationExpression);
return true;
}
if (name == PropertyName.@MyButtonOkWithImplicitCallableCreationExpression) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonOkWithImplicitCallableCreationExpression);
return true;
}
if (name == PropertyName.@MyButtonOkWithCallableFromExpression) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonOkWithCallableFromExpression);
return true;
}
if (name == PropertyName.@_backingField) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@_backingField);
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.@_backingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithCallableCreationExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithImplicitCallableCreationExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithCallableFromExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
return properties;
}
#pragma warning restore CS0109
}

View File

@ -4,5 +4,5 @@ using Godot.Collections;
public partial class ExportDiagnostics_GD0108 : Node
{
[ExportToolButton("")]
public Callable {|GD0108:MyButton|};
public Callable {|GD0108:MyButton|} => new Callable();
}

View File

@ -5,5 +5,5 @@ using Godot.Collections;
public partial class ExportDiagnostics_GD0109 : Node
{
[Export, ExportToolButton("")]
public Callable {|GD0109:MyButton|};
public Callable {|GD0109:MyButton|} => new Callable();
}

View File

@ -5,5 +5,5 @@ using Godot.Collections;
public partial class ExportDiagnostics_GD0110 : Node
{
[ExportToolButton("")]
public string {|GD0110:MyButton|};
public int {|GD0110:MyButton|} => new();
}

View File

@ -0,0 +1,29 @@
using Godot;
using Godot.Collections;
[Tool]
public partial class ExportDiagnostics_GD0111 : Node
{
private Callable _backingField;
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGet|} { get; }
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGetSet|} { get; set; }
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGetWithBackingField|} { get => _backingField; }
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGetSetWithBackingField|} { get => _backingField; set => _backingField = value; }
[ExportToolButton("")]
public Callable MyButtonOkWithCallableCreationExpression => new Callable(this, "");
[ExportToolButton("")]
public Callable MyButtonOkWithImplicitCallableCreationExpression => new(this, "");
[ExportToolButton("")]
public Callable MyButtonOkWithCallableFromExpression => Callable.From(null);
}

View File

@ -6,3 +6,4 @@ GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](ht
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)
GD0111 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0111.html)

View File

@ -137,6 +137,16 @@ namespace Godot.SourceGenerators
"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 ExportToolButtonMustBeExpressionBodiedProperty =
new DiagnosticDescriptor(id: "GD0111",
title: "The exported tool button must be an expression-bodied property",
messageFormat: "The exported tool button '{0}' must be an expression-bodied property",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The exported tool button must be an expression-bodied property. The '[ExportToolButton]' attribute is only supported on expression-bodied properties with a 'new Callable(...)' or 'Callable.From(...)' expression.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0111"));
public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule =
new DiagnosticDescriptor(id: "GD0201",
title: "The name of the delegate must end with 'EventHandler'",

View File

@ -4,6 +4,7 @@ namespace Godot.SourceGenerators
{
public const string GodotObject = "Godot.GodotObject";
public const string Node = "Godot.Node";
public const string Callable = "Godot.Callable";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
public const string ExportAttr = "Godot.ExportAttribute";
public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute";

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
@ -465,6 +466,94 @@ namespace Godot.SourceGenerators
return null;
}
if (exportToolButtonAttr != null && propertySymbol != null)
{
if (!PropertyIsExpressionBodiedAndReturnsNewCallable(context.Compilation, propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(
Common.ExportToolButtonMustBeExpressionBodiedProperty,
propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
propertySymbol.ToDisplayString()
));
return null;
}
static bool PropertyIsExpressionBodiedAndReturnsNewCallable(Compilation compilation, IPropertySymbol? propertySymbol)
{
if (propertySymbol == null)
{
return false;
}
var propertyDeclarationSyntax = propertySymbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault();
if (propertyDeclarationSyntax == null || propertyDeclarationSyntax.Initializer != null)
{
return false;
}
if (propertyDeclarationSyntax.AccessorList != null)
{
var accessors = propertyDeclarationSyntax.AccessorList.Accessors;
foreach (var accessor in accessors)
{
if (!accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
{
// Only getters are allowed.
return false;
}
if (!ExpressionBodyReturnsNewCallable(compilation, accessor.ExpressionBody))
{
return false;
}
}
}
else if (!ExpressionBodyReturnsNewCallable(compilation, propertyDeclarationSyntax.ExpressionBody))
{
return false;
}
return true;
}
static bool ExpressionBodyReturnsNewCallable(Compilation compilation, ArrowExpressionClauseSyntax? expressionSyntax)
{
if (expressionSyntax == null)
{
return false;
}
var semanticModel = compilation.GetSemanticModel(expressionSyntax.SyntaxTree);
switch (expressionSyntax.Expression)
{
case ImplicitObjectCreationExpressionSyntax creationExpression:
// We already validate that the property type must be 'Callable'
// so we can assume this constructor is valid.
return true;
case ObjectCreationExpressionSyntax creationExpression:
var typeSymbol = semanticModel.GetSymbolInfo(creationExpression.Type).Symbol as ITypeSymbol;
if (typeSymbol != null)
{
return typeSymbol.FullQualifiedNameOmitGlobal() == GodotClasses.Callable;
}
break;
case InvocationExpressionSyntax invocationExpression:
var methodSymbol = semanticModel.GetSymbolInfo(invocationExpression).Symbol as IMethodSymbol;
if (methodSymbol != null && methodSymbol.Name == "From")
{
return methodSymbol.ContainingType.FullQualifiedNameOmitGlobal() == GodotClasses.Callable;
}
break;
}
return false;
}
}
var memberType = propertySymbol?.Type ?? fieldSymbol!.Type;
var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;

View File

@ -7,7 +7,7 @@ namespace Godot
/// <summary>
/// Exports the annotated <see cref="Callable"/> as a clickable button.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property)]
public sealed class ExportToolButtonAttribute : Attribute
{
/// <summary>

View File

@ -52,16 +52,16 @@ public:
virtual void set_use_edge_connections(bool p_enabled) {}
virtual bool get_use_edge_connections() const { return false; }
void set_navigation_layers(uint32_t p_navigation_layers) { navigation_layers = p_navigation_layers; }
virtual void set_navigation_layers(uint32_t p_navigation_layers) {}
uint32_t get_navigation_layers() const { return navigation_layers; }
void set_enter_cost(real_t p_enter_cost) { enter_cost = MAX(p_enter_cost, 0.0); }
virtual void set_enter_cost(real_t p_enter_cost) {}
real_t get_enter_cost() const { return enter_cost; }
void set_travel_cost(real_t p_travel_cost) { travel_cost = MAX(p_travel_cost, 0.0); }
virtual void set_travel_cost(real_t p_travel_cost) {}
real_t get_travel_cost() const { return travel_cost; }
void set_owner_id(ObjectID p_owner_id) { owner_id = p_owner_id; }
virtual void set_owner_id(ObjectID p_owner_id) {}
ObjectID get_owner_id() const { return owner_id; }
virtual ~NavBase() {}

View File

@ -94,6 +94,48 @@ void NavLink::set_end_position(const Vector3 p_position) {
request_sync();
}
void NavLink::set_navigation_layers(uint32_t p_navigation_layers) {
if (navigation_layers == p_navigation_layers) {
return;
}
navigation_layers = p_navigation_layers;
link_dirty = true;
request_sync();
}
void NavLink::set_enter_cost(real_t p_enter_cost) {
real_t new_enter_cost = MAX(p_enter_cost, 0.0);
if (enter_cost == new_enter_cost) {
return;
}
enter_cost = new_enter_cost;
link_dirty = true;
request_sync();
}
void NavLink::set_travel_cost(real_t p_travel_cost) {
real_t new_travel_cost = MAX(p_travel_cost, 0.0);
if (travel_cost == new_travel_cost) {
return;
}
travel_cost = new_travel_cost;
link_dirty = true;
request_sync();
}
void NavLink::set_owner_id(ObjectID p_owner_id) {
if (owner_id == p_owner_id) {
return;
}
owner_id = p_owner_id;
link_dirty = true;
request_sync();
}
bool NavLink::is_dirty() const {
return link_dirty;
}

View File

@ -86,6 +86,12 @@ public:
return end_position;
}
// NavBase properties.
virtual void set_navigation_layers(uint32_t p_navigation_layers) override;
virtual void set_enter_cost(real_t p_enter_cost) override;
virtual void set_travel_cost(real_t p_travel_cost) override;
virtual void set_owner_id(ObjectID p_owner_id) override;
bool is_dirty() const;
void sync();
void request_sync();

View File

@ -141,10 +141,54 @@ Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniform
return NavMeshQueries3D::polygons_get_random_point(get_polygons(), p_navigation_layers, p_uniformly);
}
void NavRegion::set_navigation_layers(uint32_t p_navigation_layers) {
if (navigation_layers == p_navigation_layers) {
return;
}
navigation_layers = p_navigation_layers;
region_dirty = true;
request_sync();
}
void NavRegion::set_enter_cost(real_t p_enter_cost) {
real_t new_enter_cost = MAX(p_enter_cost, 0.0);
if (enter_cost == new_enter_cost) {
return;
}
enter_cost = new_enter_cost;
region_dirty = true;
request_sync();
}
void NavRegion::set_travel_cost(real_t p_travel_cost) {
real_t new_travel_cost = MAX(p_travel_cost, 0.0);
if (travel_cost == new_travel_cost) {
return;
}
travel_cost = new_travel_cost;
region_dirty = true;
request_sync();
}
void NavRegion::set_owner_id(ObjectID p_owner_id) {
if (owner_id == p_owner_id) {
return;
}
owner_id = p_owner_id;
region_dirty = true;
request_sync();
}
bool NavRegion::sync() {
RWLockWrite write_lock(region_rwlock);
bool something_changed = polygons_dirty /* || something_dirty? */;
bool something_changed = region_dirty || polygons_dirty;
region_dirty = false;
update_polygons();

View File

@ -48,6 +48,7 @@ class NavRegion : public NavBase {
bool use_edge_connections = true;
bool region_dirty = true;
bool polygons_dirty = true;
LocalVector<gd::Polygon> navmesh_polygons;
@ -77,10 +78,8 @@ public:
return map;
}
void set_use_edge_connections(bool p_enabled);
bool get_use_edge_connections() const {
return use_edge_connections;
}
virtual void set_use_edge_connections(bool p_enabled) override;
virtual bool get_use_edge_connections() const override { return use_edge_connections; }
void set_transform(Transform3D transform);
const Transform3D &get_transform() const {
@ -100,6 +99,12 @@ public:
real_t get_surface_area() const { return surface_area; }
AABB get_bounds() const { return bounds; }
// NavBase properties.
virtual void set_navigation_layers(uint32_t p_navigation_layers) override;
virtual void set_enter_cost(real_t p_enter_cost) override;
virtual void set_travel_cost(real_t p_travel_cost) override;
virtual void set_owner_id(ObjectID p_owner_id) override;
bool sync();
void request_sync();
void cancel_sync_request();

View File

@ -69,6 +69,8 @@ env_svg.Prepend(CPPPATH=[thirdparty_dir + "inc"])
# Enable ThorVG static object linking.
env_svg.Append(CPPDEFINES=["TVG_STATIC"])
# Explicit support for embedded images in svg.
env_svg.Append(CPPDEFINES=["THORVG_FILE_IO_SUPPORT"])
env_thirdparty = env_svg.Clone()
env_thirdparty.disable_warnings()

View File

@ -39,31 +39,9 @@
int VideoStreamPlaybackTheora::buffer_data() {
char *buffer = ogg_sync_buffer(&oy, 4096);
#ifdef THEORA_USE_THREAD_STREAMING
int read;
do {
thread_sem->post();
read = MIN(ring_buffer.data_left(), 4096);
if (read) {
ring_buffer.read((uint8_t *)buffer, read);
ogg_sync_wrote(&oy, read);
} else {
OS::get_singleton()->delay_usec(100);
}
} while (read == 0);
return read;
#else
uint64_t bytes = file->get_buffer((uint8_t *)buffer, 4096);
ogg_sync_wrote(&oy, bytes);
return (bytes);
#endif
}
int VideoStreamPlaybackTheora::queue_page(ogg_page *page) {
@ -82,34 +60,24 @@ int VideoStreamPlaybackTheora::queue_page(ogg_page *page) {
return 0;
}
void VideoStreamPlaybackTheora::video_write() {
th_ycbcr_buffer yuv;
th_decode_ycbcr_out(td, yuv);
void VideoStreamPlaybackTheora::video_write(th_ycbcr_buffer yuv) {
uint8_t *w = frame_data.ptrw();
char *dst = (char *)w;
uint32_t y_offset = region.position.y * yuv[0].stride + region.position.x;
uint32_t uv_offset = region.position.y * yuv[1].stride + region.position.x;
int pitch = 4;
frame_data.resize(size.x * size.y * pitch);
{
uint8_t *w = frame_data.ptrw();
char *dst = (char *)w;
if (px_fmt == TH_PF_444) {
yuv444_2_rgb8888((uint8_t *)dst, (uint8_t *)yuv[0].data, (uint8_t *)yuv[1].data, (uint8_t *)yuv[2].data, size.x, size.y, yuv[0].stride, yuv[1].stride, size.x << 2);
} else if (px_fmt == TH_PF_422) {
yuv422_2_rgb8888((uint8_t *)dst, (uint8_t *)yuv[0].data, (uint8_t *)yuv[1].data, (uint8_t *)yuv[2].data, size.x, size.y, yuv[0].stride, yuv[1].stride, size.x << 2);
} else if (px_fmt == TH_PF_420) {
yuv420_2_rgb8888((uint8_t *)dst, (uint8_t *)yuv[0].data, (uint8_t *)yuv[1].data, (uint8_t *)yuv[2].data, size.x, size.y, yuv[0].stride, yuv[1].stride, size.x << 2);
}
format = Image::FORMAT_RGBA8;
if (px_fmt == TH_PF_444) {
yuv444_2_rgb8888((uint8_t *)dst, (uint8_t *)yuv[0].data + y_offset, (uint8_t *)yuv[1].data + uv_offset, (uint8_t *)yuv[2].data + uv_offset, region.size.x, region.size.y, yuv[0].stride, yuv[1].stride, region.size.x << 2);
} else if (px_fmt == TH_PF_422) {
yuv422_2_rgb8888((uint8_t *)dst, (uint8_t *)yuv[0].data + y_offset, (uint8_t *)yuv[1].data + uv_offset, (uint8_t *)yuv[2].data + uv_offset, region.size.x, region.size.y, yuv[0].stride, yuv[1].stride, region.size.x << 2);
} else if (px_fmt == TH_PF_420) {
yuv420_2_rgb8888((uint8_t *)dst, (uint8_t *)yuv[0].data + y_offset, (uint8_t *)yuv[1].data + uv_offset, (uint8_t *)yuv[2].data + uv_offset, region.size.x, region.size.y, yuv[0].stride, yuv[1].stride, region.size.x << 2);
}
Ref<Image> img = memnew(Image(size.x, size.y, false, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation
Ref<Image> img;
img.instantiate(region.size.x, region.size.y, false, Image::FORMAT_RGBA8, frame_data); //zero copy image creation
texture->update(img); //zero copy send to rendering server
frames_pending = 1;
}
void VideoStreamPlaybackTheora::clear() {
@ -136,20 +104,15 @@ void VideoStreamPlaybackTheora::clear() {
}
ogg_sync_clear(&oy);
#ifdef THEORA_USE_THREAD_STREAMING
thread_exit = true;
thread_sem->post(); //just in case
thread.wait_to_finish();
ring_buffer.clear();
#endif
theora_p = 0;
vorbis_p = 0;
videobuf_ready = 0;
frames_pending = 0;
videobuf_time = 0;
next_frame_time = 0;
current_frame_time = 0;
theora_eos = false;
vorbis_eos = false;
video_ready = false;
video_done = false;
audio_done = false;
file.unref();
playing = false;
@ -164,17 +127,6 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
file = FileAccess::open(p_file, FileAccess::READ);
ERR_FAIL_COND_MSG(file.is_null(), "Cannot open file '" + p_file + "'.");
#ifdef THEORA_USE_THREAD_STREAMING
thread_exit = false;
thread_eof = false;
//pre-fill buffer
int to_read = ring_buffer.space_left();
uint64_t read = file->get_buffer(read_buffer.ptr(), to_read);
ring_buffer.write(read_buffer.ptr(), read);
thread.start(_streaming_thread, this);
#endif
ogg_sync_init(&oy);
/* init supporting Vorbis structures needed in header parsing */
@ -327,16 +279,18 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
th_decode_ctl(td, TH_DECCTL_SET_PPLEVEL, &pp_level, sizeof(pp_level));
pp_inc = 0;
int w;
int h;
w = ((ti.pic_x + ti.frame_width + 1) & ~1) - (ti.pic_x & ~1);
h = ((ti.pic_y + ti.frame_height + 1) & ~1) - (ti.pic_y & ~1);
size.x = w;
size.y = h;
size.x = ti.frame_width;
size.y = ti.frame_height;
region.position.x = ti.pic_x;
region.position.y = ti.pic_y;
region.size.x = ti.pic_width;
region.size.y = ti.pic_height;
Ref<Image> img = Image::create_empty(w, h, false, Image::FORMAT_RGBA8);
Ref<Image> img = Image::create_empty(region.size.x, region.size.y, false, Image::FORMAT_RGBA8);
texture->set_image(img);
frame_data.resize(region.size.x * region.size.y * 4);
frame_duration = (double)ti.fps_denominator / ti.fps_numerator;
} else {
/* tear down the partial theora setup */
th_info_clear(&ti);
@ -358,7 +312,8 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
playing = false;
buffering = true;
time = 0;
audio_frames_wrote = 0;
video_done = !theora_p;
audio_done = !vorbis_p;
}
double VideoStreamPlaybackTheora::get_time() const {
@ -378,68 +333,41 @@ void VideoStreamPlaybackTheora::update(double p_delta) {
}
if (!playing || paused) {
//printf("not playing\n");
return;
}
#ifdef THEORA_USE_THREAD_STREAMING
thread_sem->post();
#endif
time += p_delta;
if (videobuf_time > get_time()) {
return; //no new frames need to be produced
}
bool frame_done = false;
bool audio_done = !vorbis_p;
while (!frame_done || (!audio_done && !vorbis_eos)) {
//a frame needs to be produced
double comp_time = get_time();
bool audio_ready = false;
// Read data until we fill the audio buffer and get a new video frame.
while ((!audio_ready && !audio_done) || (!video_ready && !video_done)) {
ogg_packet op;
bool no_theora = false;
bool buffer_full = false;
while (vorbis_p && !audio_done && !buffer_full) {
int ret;
while (!audio_ready && !audio_done) {
float **pcm;
/* if there's pending, decoded audio, grab it */
ret = vorbis_synthesis_pcmout(&vd, &pcm);
int ret = vorbis_synthesis_pcmout(&vd, &pcm);
if (ret > 0) {
const int AUXBUF_LEN = 4096;
int to_read = ret;
float aux_buffer[AUXBUF_LEN];
while (to_read) {
int m = MIN(AUXBUF_LEN / vi.channels, to_read);
int count = 0;
for (int j = 0; j < m; j++) {
for (int i = 0; i < vi.channels; i++) {
aux_buffer[count++] = pcm[i][j];
}
}
if (mix_callback) {
int mixed = mix_callback(mix_udata, aux_buffer, m);
to_read -= mixed;
if (mixed != m) { //could mix no more
buffer_full = true;
break;
}
} else {
to_read -= m; //just pretend we sent the audio
int mixed = mix_callback(mix_udata, aux_buffer, m);
to_read -= mixed;
if (mixed != m) { //could mix no more
audio_ready = true;
break;
}
}
vorbis_synthesis_read(&vd, ret - to_read);
audio_frames_wrote += ret - to_read;
} else {
/* no pending audio; is there a pending packet to decode? */
if (ogg_stream_packetout(&vo, &op) > 0) {
@ -447,19 +375,13 @@ void VideoStreamPlaybackTheora::update(double p_delta) {
vorbis_synthesis_blockin(&vd, &vb);
}
} else { /* we need more data; break out to suck in another page */
audio_done = vorbis_eos;
break;
}
}
audio_done = videobuf_time < (audio_frames_wrote / float(vi.rate));
if (buffer_full) {
break;
}
}
while (theora_p && !frame_done) {
/* theora is one in, one out... */
while (!video_ready && !video_done) {
if (ogg_stream_packetout(&to, &op) > 0) {
/*HACK: This should be set after a seek or a gap, but we might not have
a granulepos for the first packet (we only have them for the last
@ -472,62 +394,37 @@ void VideoStreamPlaybackTheora::update(double p_delta) {
sizeof(op.granulepos));
}
ogg_int64_t videobuf_granulepos;
if (th_decode_packetin(td, &op, &videobuf_granulepos) == 0) {
videobuf_time = th_granule_time(td, videobuf_granulepos);
//printf("frame time %f, play time %f, ready %i\n", (float)videobuf_time, get_time(), videobuf_ready);
/* is it already too old to be useful? This is only actually
useful cosmetically after a SIGSTOP. Note that we have to
decode the frame even if we don't show it (for now) due to
keyframing. Soon enough libtheora will be able to deal
with non-keyframe seeks. */
if (videobuf_time >= get_time()) {
frame_done = true;
int ret = th_decode_packetin(td, &op, &videobuf_granulepos);
if (ret == 0 || ret == TH_DUPFRAME) {
next_frame_time = th_granule_time(td, videobuf_granulepos);
if (next_frame_time > comp_time) {
dup_frame = (ret == TH_DUPFRAME);
video_ready = true;
} else {
/*If we are too slow, reduce the pp level.*/
pp_inc = pp_level > 0 ? -1 : 0;
}
}
} else {
no_theora = true;
} else { /* we need more data; break out to suck in another page */
video_done = theora_eos;
break;
}
}
#ifdef THEORA_USE_THREAD_STREAMING
if (file.is_valid() && thread_eof && no_theora && theora_eos && ring_buffer.data_left() == 0) {
#else
if (file.is_valid() && /*!videobuf_ready && */ no_theora && theora_eos) {
#endif
//printf("video done, stopping\n");
stop();
return;
}
if (!frame_done || !audio_done) {
//what's the point of waiting for audio to grab a page?
buffer_data();
while (ogg_sync_pageout(&oy, &og) > 0) {
queue_page(&og);
if (!video_ready || !audio_ready) {
int ret = buffer_data();
if (ret > 0) {
while (ogg_sync_pageout(&oy, &og) > 0) {
queue_page(&og);
}
} else {
vorbis_eos = true;
theora_eos = true;
break;
}
}
/* If playback has begun, top audio buffer off immediately. */
//if(stateflag) audio_write_nonblocking();
/* are we at or past time for this video frame? */
if (videobuf_ready && videobuf_time <= get_time()) {
//video_write();
//videobuf_ready=0;
} else {
//printf("frame at %f not ready (time %f), ready %i\n", (float)videobuf_time, get_time(), videobuf_ready);
}
double tdiff = videobuf_time - get_time();
double tdiff = next_frame_time - comp_time;
/*If we have lots of extra time, increase the post-processing level.*/
if (tdiff > ti.fps_denominator * 0.25 / ti.fps_numerator) {
pp_inc = pp_level < pp_level_max ? 1 : 0;
@ -536,7 +433,22 @@ void VideoStreamPlaybackTheora::update(double p_delta) {
}
}
video_write();
if (!video_ready && video_done && audio_done) {
stop();
return;
}
// Wait for the last frame to end before rendering the next one.
if (video_ready && comp_time >= current_frame_time) {
if (!dup_frame) {
th_ycbcr_buffer yuv;
th_decode_ycbcr_out(td, yuv);
video_write(yuv);
}
dup_frame = false;
video_ready = false;
current_frame_time = next_frame_time;
}
}
void VideoStreamPlaybackTheora::play() {
@ -596,44 +508,11 @@ int VideoStreamPlaybackTheora::get_mix_rate() const {
return vi.rate;
}
#ifdef THEORA_USE_THREAD_STREAMING
void VideoStreamPlaybackTheora::_streaming_thread(void *ud) {
VideoStreamPlaybackTheora *vs = static_cast<VideoStreamPlaybackTheora *>(ud);
while (!vs->thread_exit) {
//just fill back the buffer
if (!vs->thread_eof) {
int to_read = vs->ring_buffer.space_left();
if (to_read > 0) {
uint64_t read = vs->file->get_buffer(vs->read_buffer.ptr(), to_read);
vs->ring_buffer.write(vs->read_buffer.ptr(), read);
vs->thread_eof = vs->file->eof_reached();
}
}
vs->thread_sem->wait();
}
}
#endif
VideoStreamPlaybackTheora::VideoStreamPlaybackTheora() {
texture.instantiate();
#ifdef THEORA_USE_THREAD_STREAMING
int rb_power = nearest_shift(RB_SIZE_KB * 1024);
ring_buffer.resize(rb_power);
read_buffer.resize(RB_SIZE_KB * 1024);
thread_sem = Semaphore::create();
#endif
}
VideoStreamPlaybackTheora::~VideoStreamPlaybackTheora() {
#ifdef THEORA_USE_THREAD_STREAMING
memdelete(thread_sem);
#endif
clear();
}

View File

@ -41,27 +41,20 @@
class ImageTexture;
//#define THEORA_USE_THREAD_STREAMING
class VideoStreamPlaybackTheora : public VideoStreamPlayback {
GDCLASS(VideoStreamPlaybackTheora, VideoStreamPlayback);
enum {
MAX_FRAMES = 4,
};
//Image frames[MAX_FRAMES];
Image::Format format = Image::Format::FORMAT_L8;
Vector<uint8_t> frame_data;
int frames_pending = 0;
Ref<FileAccess> file;
String file_name;
int audio_frames_wrote = 0;
Point2i size;
Rect2i region;
int buffer_data();
int queue_page(ogg_page *page);
void video_write();
void video_write(th_ycbcr_buffer yuv);
double get_time() const;
bool theora_eos = false;
@ -79,43 +72,30 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback {
vorbis_block vb;
vorbis_comment vc;
th_pixel_fmt px_fmt;
double videobuf_time = 0;
int pp_inc = 0;
double frame_duration;
int theora_p = 0;
int vorbis_p = 0;
int pp_level_max = 0;
int pp_level = 0;
int videobuf_ready = 0;
int pp_inc = 0;
bool playing = false;
bool buffering = false;
bool paused = false;
bool dup_frame = false;
bool video_ready = false;
bool video_done = false;
bool audio_done = false;
double last_update_time = 0;
double time = 0;
double next_frame_time = 0;
double current_frame_time = 0;
double delay_compensation = 0;
Ref<ImageTexture> texture;
bool paused = false;
#ifdef THEORA_USE_THREAD_STREAMING
enum {
RB_SIZE_KB = 1024
};
RingBuffer<uint8_t> ring_buffer;
Vector<uint8_t> read_buffer;
bool thread_eof = false;
Semaphore *thread_sem = nullptr;
Thread thread;
SafeFlag thread_exit;
static void _streaming_thread(void *ud);
#endif
int audio_track = 0;
protected:

View File

@ -70,6 +70,7 @@ void JavaObject::_bind_methods() {
void JavaClassWrapper::_bind_methods() {
ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap);
ClassDB::bind_method(D_METHOD("get_exception"), &JavaClassWrapper::get_exception);
}
#if !defined(ANDROID_ENABLED)

View File

@ -271,6 +271,8 @@ class JavaClassWrapper : public Object {
bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
#endif
Ref<JavaObject> exception;
Ref<JavaClass> _wrap(const String &p_class, bool p_allow_private_methods_access);
static JavaClassWrapper *singleton;
@ -285,6 +287,10 @@ public:
return _wrap(p_class, false);
}
Ref<JavaObject> get_exception() {
return exception;
}
#ifdef ANDROID_ENABLED
Ref<JavaClass> wrap_jclass(jclass p_class, bool p_allow_private_methods_access = false);
#endif

View File

@ -12,7 +12,8 @@ ext.versions = [
javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
ndkVersion : '23.2.8568313',
splashscreenVersion: '1.0.1'
splashscreenVersion: '1.0.1',
openxrVendorsVersion: '3.1.2-stable'
]

View File

@ -188,7 +188,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk15to18:1.78"
// Meta dependencies
horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable"
horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:$versions.openxrVendorsVersion"
// Pico dependencies
picoosImplementation "org.godotengine:godot-openxr-vendors-pico:3.0.1-stable"
picoosImplementation "org.godotengine:godot-openxr-vendors-pico:$versions.openxrVendorsVersion"
}

View File

@ -30,6 +30,7 @@
package org.godotengine.editor.embed
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.Gravity
import android.view.MotionEvent
@ -63,6 +64,9 @@ class EmbeddedGodotGame : GodotGame() {
private var layoutWidthInPx = 0
private var layoutHeightInPx = 0
private var gameRequestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
private var isFullscreen = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -81,13 +85,26 @@ class EmbeddedGodotGame : GodotGame() {
window.attributes = layoutParams
}
override fun setRequestedOrientation(requestedOrientation: Int) {
// Allow orientation change only if fullscreen mode is active
// or if the requestedOrientation is landscape (i.e switching to default).
if (isFullscreen || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE) {
super.setRequestedOrientation(requestedOrientation)
} else {
// Cache the requestedOrientation to apply when switching to fullscreen.
gameRequestedOrientation = requestedOrientation
}
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_OUTSIDE -> {
if (gameMenuFragment?.isAlwaysOnTop() == true) {
enterPiPMode()
} else {
minimizeGameWindow()
if (!isFullscreen) {
if (gameMenuFragment?.isAlwaysOnTop() == true) {
enterPiPMode()
} else {
minimizeGameWindow()
}
}
}
@ -127,12 +144,18 @@ class EmbeddedGodotGame : GodotGame() {
override fun onFullScreenUpdated(enabled: Boolean) {
godot?.enableImmersiveMode(enabled)
isFullscreen = enabled
if (enabled) {
layoutWidthInPx = FULL_SCREEN_WIDTH
layoutHeightInPx = FULL_SCREEN_HEIGHT
requestedOrientation = gameRequestedOrientation
} else {
layoutWidthInPx = defaultWidthInPx
layoutHeightInPx = defaultHeightInPx
// Cache the last used orientation in fullscreen to reapply when re-entering fullscreen.
gameRequestedOrientation = requestedOrientation
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
updateWindowDimensions(layoutWidthInPx, layoutHeightInPx)
}

View File

@ -517,6 +517,20 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
env->DeleteLocalRef(E);
}
jobject exception = env->ExceptionOccurred();
if (exception) {
env->ExceptionClear();
jclass java_class = env->GetObjectClass(exception);
Ref<JavaClass> java_class_wrapped = JavaClassWrapper::singleton->wrap_jclass(java_class);
env->DeleteLocalRef(java_class);
JavaClassWrapper::singleton->exception.instantiate(java_class_wrapped, exception);
env->DeleteLocalRef(exception);
} else {
JavaClassWrapper::singleton->exception.unref();
}
return success;
}

View File

@ -198,6 +198,19 @@ static const DataCollectionInfo data_collect_purpose_info[] = {
{ "Other", "NSPrivacyCollectedDataTypePurposeOther" },
};
static const String export_method_string[] = {
"app-store",
"development",
"ad-hoc",
"enterprise"
};
static const String storyboard_image_scale_mode[] = {
"center",
"scaleAspectFit",
"scaleAspectFill",
"scaleToFill"
};
String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
if (p_preset) {
if (p_name == "application/app_store_team_id") {
@ -402,19 +415,28 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color()));
}
HashMap<String, Variant> EditorExportPlatformIOS::get_custom_project_settings(const Ref<EditorExportPreset> &p_preset) const {
HashMap<String, Variant> settings;
int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
String value;
switch (image_scale_mode) {
case 0: {
String logo_path = GLOBAL_GET("application/boot_splash/image");
bool is_on = GLOBAL_GET("application/boot_splash/fullsize");
// If custom logo is not specified, Godot does not scale default one, so we should do the same.
value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center";
} break;
default: {
value = storyboard_image_scale_mode[image_scale_mode - 1];
}
}
settings["ios/launch_screen_image_mode"] = value;
return settings;
}
void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug) {
static const String export_method_string[] = {
"app-store",
"development",
"ad-hoc",
"enterprise"
};
static const String storyboard_image_scale_mode[] = {
"center",
"scaleAspectFit",
"scaleAspectFill",
"scaleToFill"
};
String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug");
String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release");
bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer" && dbg_sign_id != "iPhone Distribution");

View File

@ -203,6 +203,8 @@ public:
return list;
}
virtual HashMap<String, Variant> get_custom_project_settings(const Ref<EditorExportPreset> &p_preset) const override;
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;

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